From d08896d5e6b194320f2022e30b178e264152b6e7 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Sun, 12 May 2024 19:35:24 -0400 Subject: [PATCH 01/12] Added Email to responseType --- .../DataWriters/Emails/EmailService.cs | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index ce4c7db34b..7e72337b08 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -124,6 +124,13 @@ public class DataSourceResponse public EmailDataSource Model { get; set; } } + + public class EmailResponse + { + public List DataSources { get; set; } + public string Body { get; set; } + public string Subject { get; set; } + } #endregion #region [ Constructors ] @@ -160,30 +167,31 @@ public bool SendEmail(EmailType email, List eventIDs, Event evt, DateTime x return true; } - public void SendEmail(EmailType email, Event evt, List recipients, List dataSourceResponses) => - SendEmail(email, evt, recipients, new DateTime(), new List(), false, dataSourceResponses); + public void SendEmail(EmailType email, Event evt, List recipients, out EmailResponse response, bool saveToFile = false) => + SendEmail(email, evt, recipients, new DateTime(), new List(), saveToFile, out response); public void SendEmail(EmailType email, Event evt, List recipients) => - SendEmail(email, evt, recipients, new DateTime(), new List(), false, null); + SendEmail(email, evt, recipients, new DateTime(), new List(), false, out EmailResponse response); private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile) => - SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, null); + SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, out EmailResponse response); - private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, List dataSourceResponses) + private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, out EmailResponse response) { - List attachments = new List(); - List responses = dataSourceResponses ?? new List(); + response = new EmailResponse() { }; + + List attachments = new List(); try { - LoadDataSources(email, evt, responses); + LoadDataSources(email, evt, response.DataSources); Settings settings = new Settings(Configure); - XElement templateData = new XElement("data", responses.Select(r => r.Data)); + XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); XDocument htmlDocument = ApplyTemplate(email, templateData.ToString()); ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); ApplyImageEmbedTransform(attachments, htmlDocument); - SendEmail(recipients, htmlDocument, attachments, email, settings, (saveToFile ? email.FilePath : null)); + SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); if (eventIDs.Count() > 0) LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); } @@ -205,28 +213,31 @@ public bool SendScheduledEmail(ScheduledEmailType email, DateTime xdaNow) return true; } - public void SendScheduledEmail(ScheduledEmailType email, List recipients, List dataSourceResponses, DateTime xdaNow) => - SendScheduledEmail(email, recipients, false, dataSourceResponses, xdaNow); + public void SendScheduledEmail(ScheduledEmailType email, List recipients, out EmailResponse response, DateTime xdaNow) => + SendScheduledEmail(email, recipients, false, out response, xdaNow); public void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => - SendScheduledEmail(email, recipients, saveToFile, null, xdaNow); + SendScheduledEmail(email, recipients, saveToFile, out EmailResponse response, xdaNow); - private void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, List dataSourceResponses, DateTime xdaNow) + private void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, out EmailResponse response, DateTime xdaNow) { List attachments = new List(); - List responses = dataSourceResponses ?? new List(); + response = new EmailResponse() + { + DataSources = new List(), + }; try { - LoadDataSources(email, xdaNow, responses); + LoadDataSources(email, xdaNow, response.DataSources); Settings settings = new Settings(Configure); - XElement templateData = new XElement("data", responses.Select(r => r.Data)); + XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); XDocument htmlDocument = ApplyTemplate(email, templateData.ToString()); ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); ApplyImageEmbedTransform(attachments, htmlDocument); - SendEmail(recipients, htmlDocument, attachments, email, settings, (saveToFile ? email.FilePath : null)); + SendEmail(recipients, htmlDocument, attachments, email, settings,response, (saveToFile ? email.FilePath : null)); LoadSentEmail(email, xdaNow, recipients, htmlDocument); } finally @@ -631,13 +642,16 @@ public void ApplyImageEmbedTransform(List attachments, XDocument htm } } - private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, string filePath=null) + private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath=null) { EmailSection emailSettings = settings.EmailSettings; string smtpServer = emailSettings.SMTPServer; + email.Body = GetBody(htmlDocument); + email.Subject = GetSubject(htmlDocument, emailType); + if (string.IsNullOrEmpty(smtpServer)) - return; + return; using (SmtpClient smtpClient = CreateSmtpClient(smtpServer)) using (MailMessage emailMessage = new MailMessage()) @@ -652,8 +666,8 @@ private void SendEmail(List recipients, XDocument htmlDocument, List Date: Sun, 12 May 2024 19:35:55 -0400 Subject: [PATCH 02/12] Added endpoint to Download email file --- .../Controllers/WebAPI/EmailController.cs | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index 01c5d255b1..03f8bf2226 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -25,8 +25,10 @@ using System.Collections.Generic; using System.ComponentModel; using System.Net; +using System.Net.Http; using System.Net.Mail; using System.Security; +using System.Threading.Tasks; using System.Web.Http; using FaultData.DataWriters.Emails; using GSF.Configuration; @@ -138,7 +140,9 @@ public IHttpActionResult SendTestEmail(int emailID, string userID, int eventID) try { - emailService.SendEmail(email, evt, new List() { account.Email }, response.DataSourceResponses); + emailService.SendEmail(email, evt, new List() { account.Email }, out EmailResponse resultEmail); + response.DataSourceResponses = resultEmail.DataSources; + return Ok(response); } catch (Exception ex) @@ -149,6 +153,57 @@ public IHttpActionResult SendTestEmail(int emailID, string userID, int eventID) } } + [Route("testFile/{emailID:int}/{eventID:int}/{save:int}"), HttpGet] + public async Task TestEmailFile(int emailID, int eventID, int save) + { + Settings settings = new Settings(GetConfigurator()); + EventEmailSection eventEmailSettings = settings.EventEmailSettings; + EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + + using (AdoDataConnection connection = CreateDbConnection()) + { + Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventID); + if (evt is null) + return new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent($"Event with ID {eventID} does not exists") + }; + + + EmailType email = new TableOperations(connection).QueryRecordWhere("ID = {0}", emailID); + if (email is null) + return new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent($"Email type with ID {emailID} does not exists") + }; + + try + { + emailService.SendEmail(email, evt, new List() {}, out EmailResponse emailresponse, save > 0); + + var result = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(emailresponse.Body) + }; + + result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") + { + FileName =emailresponse.Subject + }; + + return result; + } + catch (Exception ex) + { + return new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent($"Email type does not produce valid email please check the Template") + }; + + } + } + } + [Route("testReport/{reportID:int}/{userID}/{current}"), HttpGet] public IHttpActionResult SendTestReport(int reportID, string userID, string current) { @@ -173,7 +228,8 @@ public IHttpActionResult SendTestReport(int reportID, string userID, string curr if (!DateTime.TryParse(current, out DateTime xdaNow)) xdaNow = DateTime.UtcNow; - emailService.SendScheduledEmail(report, new List() { account.Email }, response.DataSourceResponses, xdaNow); + emailService.SendScheduledEmail(report, new List() { account.Email }, out EmailResponse emailResponse, xdaNow); + response.DataSourceResponses = emailResponse.DataSources; return Ok(response); } catch (Exception ex) From d4dfec0794a749f1f8b6e215480115a8901b1994 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Mon, 13 May 2024 12:33:07 -0400 Subject: [PATCH 03/12] Clean up and fix errors --- .../Controllers/WebAPI/EmailController.cs | 77 +++++++++---------- .../DataWriters/Emails/EmailService.cs | 36 +++++---- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index 03f8bf2226..5273ca0438 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -28,6 +28,7 @@ using System.Net.Http; using System.Net.Mail; using System.Security; +using System.Threading; using System.Threading.Tasks; using System.Web.Http; using FaultData.DataWriters.Emails; @@ -142,7 +143,6 @@ public IHttpActionResult SendTestEmail(int emailID, string userID, int eventID) { emailService.SendEmail(email, evt, new List() { account.Email }, out EmailResponse resultEmail); response.DataSourceResponses = resultEmail.DataSources; - return Ok(response); } catch (Exception ex) @@ -153,55 +153,54 @@ public IHttpActionResult SendTestEmail(int emailID, string userID, int eventID) } } - [Route("testFile/{emailID:int}/{eventID:int}/{save:int}"), HttpGet] - public async Task TestEmailFile(int emailID, int eventID, int save) + [Route("testFile/{emailID:int}/{eventID:int}/{save:bool}"), HttpGet] + public async Task TestEmailFile(int emailID, int eventID, bool save, CancellationToken cancellationToken) { - Settings settings = new Settings(GetConfigurator()); - EventEmailSection eventEmailSettings = settings.EventEmailSettings; - EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + string subject = null; - using (AdoDataConnection connection = CreateDbConnection()) + IHttpActionResult RunTest() { - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventID); - if (evt is null) - return new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent($"Event with ID {eventID} does not exists") - }; - - - EmailType email = new TableOperations(connection).QueryRecordWhere("ID = {0}", emailID); - if (email is null) - return new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent($"Email type with ID {emailID} does not exists") - }; + Settings settings = new Settings(GetConfigurator()); + EventEmailSection eventEmailSettings = settings.EventEmailSettings; + EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); - try + using (AdoDataConnection connection = CreateDbConnection()) { - emailService.SendEmail(email, evt, new List() {}, out EmailResponse emailresponse, save > 0); + Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventID); + if (evt is null) + return BadRequest($"Event with ID {eventID} does not exists"); - var result = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(emailresponse.Body) - }; - result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") - { - FileName =emailresponse.Subject - }; + EmailType email = new TableOperations(connection).QueryRecordWhere("ID = {0}", emailID); + if (email is null) + return BadRequest($"Email type with ID {emailID} does not exists"); - return result; - } - catch (Exception ex) - { - return new HttpResponseMessage(HttpStatusCode.InternalServerError) + try { - Content = new StringContent($"Email type does not produce valid email please check the Template") - }; - + emailService.SendEmail(email, evt, new List() { }, save, out EmailResponse emailResponse); + subject = emailResponse.Subject; + return Ok(emailResponse.Body); + } + catch (Exception ex) + { + Exception wrapper = new Exception("Email type does not produce valid email please check the Template", ex); + return UnprocessibleEntity(wrapper.ToString()); + } } } + + IHttpActionResult testResult = RunTest(); + HttpResponseMessage httpResponse = await testResult.ExecuteAsync(cancellationToken); + + if (!(subject is null)) + { + httpResponse.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") + { + FileName = subject + }; + } + + return httpResponse; } [Route("testReport/{reportID:int}/{userID}/{current}"), HttpGet] diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 7e72337b08..a6427b256d 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -127,10 +127,11 @@ public class DataSourceResponse public class EmailResponse { - public List DataSources { get; set; } + public List DataSources { get; } = new List(); public string Body { get; set; } public string Subject { get; set; } } + #endregion #region [ Constructors ] @@ -167,21 +168,24 @@ public bool SendEmail(EmailType email, List eventIDs, Event evt, DateTime x return true; } - public void SendEmail(EmailType email, Event evt, List recipients, out EmailResponse response, bool saveToFile = false) => + public void SendEmail(EmailType email, Event evt, List recipients, bool saveToFile, out EmailResponse response) => SendEmail(email, evt, recipients, new DateTime(), new List(), saveToFile, out response); + public void SendEmail(EmailType email, Event evt, List recipients, out EmailResponse response) => + SendEmail(email, evt, recipients, new DateTime(), new List(), false, out response); + public void SendEmail(EmailType email, Event evt, List recipients) => - SendEmail(email, evt, recipients, new DateTime(), new List(), false, out EmailResponse response); + SendEmail(email, evt, recipients, new DateTime(), new List(), false, out EmailResponse _); private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile) => - SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, out EmailResponse response); + SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, out EmailResponse _); - private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, out EmailResponse response) + private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, out EmailResponse response) { + List attachments = new List(); - response = new EmailResponse() { }; + response = new EmailResponse(); - List attachments = new List(); try { LoadDataSources(email, evt, response.DataSources); @@ -217,15 +221,13 @@ public void SendScheduledEmail(ScheduledEmailType email, List recipients SendScheduledEmail(email, recipients, false, out response, xdaNow); public void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => - SendScheduledEmail(email, recipients, saveToFile, out EmailResponse response, xdaNow); + SendScheduledEmail(email, recipients, saveToFile, out EmailResponse _, xdaNow); private void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, out EmailResponse response, DateTime xdaNow) { List attachments = new List(); - response = new EmailResponse() - { - DataSources = new List(), - }; + + response = new EmailResponse(); try { @@ -234,10 +236,10 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient Settings settings = new Settings(Configure); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); XDocument htmlDocument = ApplyTemplate(email, templateData.ToString()); - + ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); ApplyImageEmbedTransform(attachments, htmlDocument); - SendEmail(recipients, htmlDocument, attachments, email, settings,response, (saveToFile ? email.FilePath : null)); + SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); LoadSentEmail(email, xdaNow, recipients, htmlDocument); } finally @@ -642,7 +644,7 @@ public void ApplyImageEmbedTransform(List attachments, XDocument htm } } - private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath=null) + private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath = null) { EmailSection emailSettings = settings.EmailSettings; string smtpServer = emailSettings.SMTPServer; @@ -651,7 +653,7 @@ private void SendEmail(List recipients, XDocument htmlDocument, List recipients, XDocument htmlDocument, List Date: Wed, 15 May 2024 14:55:03 -0400 Subject: [PATCH 04/12] Refactor ScheduledEmailDataSource model to be consistent with TriggeredEmailDataSource model --- ...dEmailDataSource.cs => EmailDataSource.cs} | 5 ++-- .../Emails/ScheduledEmailDataSource.cs | 27 ------------------- .../openXDA.Model/openXDA.Model.csproj | 3 +-- 3 files changed, 4 insertions(+), 31 deletions(-) rename Source/Libraries/openXDA.Model/Emails/{TriggeredEmailDataSource.cs => EmailDataSource.cs} (91%) delete mode 100644 Source/Libraries/openXDA.Model/Emails/ScheduledEmailDataSource.cs diff --git a/Source/Libraries/openXDA.Model/Emails/TriggeredEmailDataSource.cs b/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs similarity index 91% rename from Source/Libraries/openXDA.Model/Emails/TriggeredEmailDataSource.cs rename to Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs index 13855c411f..1c9b66420f 100644 --- a/Source/Libraries/openXDA.Model/Emails/TriggeredEmailDataSource.cs +++ b/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs @@ -1,5 +1,5 @@ //****************************************************************************************************** -// TriggeredEmailDataSource.cs - Gbtc +// EmailDataSource.cs - Gbtc // // Copyright © 2017, Grid Protection Alliance. All Rights Reserved. // @@ -42,5 +42,6 @@ public class EmailDataSource public string ConfigUI { get; set; } } - public class TriggeredEmailDataSource: EmailDataSource { } + public class TriggeredEmailDataSource : EmailDataSource { } + public class ScheduledEmailDataSource : EmailDataSource { } } diff --git a/Source/Libraries/openXDA.Model/Emails/ScheduledEmailDataSource.cs b/Source/Libraries/openXDA.Model/Emails/ScheduledEmailDataSource.cs deleted file mode 100644 index 7b6c9cf4a7..0000000000 --- a/Source/Libraries/openXDA.Model/Emails/ScheduledEmailDataSource.cs +++ /dev/null @@ -1,27 +0,0 @@ -//****************************************************************************************************** -// ScheduledEmailDataSource.cs - Gbtc -// -// Copyright © 2017, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may -// not use this file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 11/9/2021 - Samuel Robinson -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.Model -{ - public class ScheduledEmailDataSource: EmailDataSource { } -} diff --git a/Source/Libraries/openXDA.Model/openXDA.Model.csproj b/Source/Libraries/openXDA.Model/openXDA.Model.csproj index a70ad86963..aba15f539c 100644 --- a/Source/Libraries/openXDA.Model/openXDA.Model.csproj +++ b/Source/Libraries/openXDA.Model/openXDA.Model.csproj @@ -108,14 +108,13 @@ - - + From 66df8f9c394a86479af4737c5e78451dacee1ee7 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Wed, 15 May 2024 14:56:34 -0400 Subject: [PATCH 05/12] Extract TriggeredEmailDataSource functions into their own classes --- .../Controllers/WebAPI/EmailController.cs | 11 ++- .../DataWriters/Emails/DataSourceResponse.cs | 38 ++++++++ .../DataWriters/Emails/EmailService.cs | 93 +------------------ .../DataWriters/Emails/TriggeredDataSource.cs | 47 ++++++++++ .../Emails/TriggeredDataSourceDefinition.cs | 58 ++++++++++++ .../Emails/TriggeredDataSourceFactory.cs | 78 ++++++++++++++++ Source/Libraries/FaultData/FaultData.csproj | 4 + .../openXDA.Model/Emails/EmailDataSource.cs | 2 +- 8 files changed, 238 insertions(+), 93 deletions(-) create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/DataSourceResponse.cs create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSource.cs create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceDefinition.cs create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceFactory.cs diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index 5273ca0438..4e5771393b 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Mail; @@ -242,14 +243,18 @@ public IHttpActionResult SendTestReport(int reportID, string userID, string curr [Route("testData/{emailID:int}/{eventID:int}"), HttpGet] public IHttpActionResult TestDataSource(int emailID, int eventID) { - EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + TriggeredDataSourceFactory factory = new TriggeredDataSourceFactory(CreateDbConnection); using (AdoDataConnection connection = CreateDbConnection()) { Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventID); EmailType email = new TableOperations(connection).QueryRecordWhere("ID = {0}", emailID); - List dataSourceResponses = new List(); - emailService.LoadDataSources(email, evt, dataSourceResponses); + List definitions = factory.LoadDataSourceDefinitions(email); + + List dataSourceResponses = definitions + .Select(definition => definition.CreateAndProcess(factory, evt)) + .ToList(); + return Ok(dataSourceResponses); } } diff --git a/Source/Libraries/FaultData/DataWriters/Emails/DataSourceResponse.cs b/Source/Libraries/FaultData/DataWriters/Emails/DataSourceResponse.cs new file mode 100644 index 0000000000..5faafae816 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/DataSourceResponse.cs @@ -0,0 +1,38 @@ +//****************************************************************************************************** +// DataSourceResponse.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Xml.Linq; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class DataSourceResponse + { + public bool Success { set; get; } + public bool Created { get; set; } + public XElement Data { get; set; } + public Exception Exception { get; set; } = null; + public EmailDataSource Model { get; set; } + } +} diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index a6427b256d..9d239b3f77 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -70,28 +70,6 @@ public DataSourceWrapper(string name, ITriggeredDataSource dataSourceTriggered = DataSourceTriggered = dataSourceTriggered; DataSourceScheduled = dataSourceScheduled; } - - public XElement TryProcess(Event evt, out Exception exception) - { - if (DataSourceTriggered is null) - { - exception = new NullReferenceException("DataSource was not created."); - Log.Debug($"Email data source {Name} was not created", exception); - return null; - } - - XElement element = null; - exception = null; - try { element = DataSourceTriggered.Process(evt); } - catch (Exception ex) { exception = ex; } - - if (!(exception is null)) - Log.Error($"Email data source {Name} failed to process", exception); - - return element; - } - - public XElement TryProcess(Event evt) => TryProcess(evt, out _); public XElement TryProcess(DateTime xdaNow) => TryProcess(xdaNow, out _); public XElement TryProcess(DateTime xdaNow, out Exception exception) @@ -115,16 +93,6 @@ public XElement TryProcess(DateTime xdaNow, out Exception exception) } } - public class DataSourceResponse - { - public bool Success { set; get; } - public bool Created { get; set; } - public XElement Data { get; set; } - public Exception Exception { get; set; } = null; - - public EmailDataSource Model { get; set; } - } - public class EmailResponse { public List DataSources { get; } = new List(); @@ -188,7 +156,10 @@ private void SendEmail(EmailType email, Event evt, List recipients, Date try { - LoadDataSources(email, evt, response.DataSources); + TriggeredDataSourceFactory factory = new TriggeredDataSourceFactory(ConnectionFactory); + List definitions = factory.LoadDataSourceDefinitions(email); + IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, evt)); + response.DataSources.AddRange(dataSourceResponses); Settings settings = new Settings(Configure); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); @@ -248,39 +219,6 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient } } - public void LoadDataSources(EmailType email, Event evt, List responses) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - /* Load All DataSources */ - TableOperations dataSourceTable = new TableOperations(connection); - TableOperations dataSourceEmailTypeTable = new TableOperations(connection); - IEnumerable dataSourceMappings = dataSourceEmailTypeTable.QueryRecordsWhere("EmailTypeID = {0}", email.ID); - - foreach (TriggeredEmailDataSourceEmailType dataSourceMapping in dataSourceMappings) - { - TriggeredEmailDataSource dataSource = dataSourceTable.QueryRecordWhere("ID = {0}", dataSourceMapping.TriggeredEmailDataSourceID); - DataSourceWrapper wrapper = CreateDataSource(dataSource, dataSourceMapping); - - Exception ex = null; - XElement data = wrapper?.TryProcess(evt, out ex); - if (wrapper is null) - ex = new Exception("Failed to create data source"); - - DataSourceResponse response = new DataSourceResponse(); - response.Model = dataSource; - response.Created = !(wrapper is null); - response.Success = !(data is null); - response.Data = data; - response.Exception = ex; - responses.Add(response); - } - - if (responses.Any(response => !response.Created)) - Log.Error("Failed to create one or more data sources for triggered email; check debug logs for details"); - } - } - public void LoadDataSources(ScheduledEmailType email, DateTime now, List responses) { using (AdoDataConnection connection = ConnectionFactory()) @@ -486,29 +424,6 @@ public XDocument ApplyTemplate(EmailTypeBase emailType, string templateData) return htmlDocument; } - private DataSourceWrapper CreateDataSource(TriggeredEmailDataSource model, TriggeredEmailDataSourceEmailType connectionModel) - { - try - { - string assemblyName = model.AssemblyName; - string typeName = model.TypeName; - PluginFactory pluginFactory = new PluginFactory(); - Type pluginType = pluginFactory.GetPluginType(assemblyName, typeName); - Type dbFactoryType = typeof(Func); - ConstructorInfo constructor = pluginType.GetConstructor(new[] { dbFactoryType }); - object[] parameters = (constructor is null) ? Array.Empty() : new object[] { ConnectionFactory }; - ITriggeredDataSource dataSource = pluginFactory.Create(assemblyName, typeName, parameters); - ConfigurationLoader configurationLoader = new ConfigurationLoader(connectionModel.ID, ConnectionFactory); - dataSource.Configure(configurationLoader.Configure); - return new DataSourceWrapper(model.Name, dataSource); - } - catch (Exception ex) - { - Log.Debug($"Failed to create ITriggeredDataSource of type {model.TypeName}", ex); - return new DataSourceWrapper(model.Name, null); - } - } - private DataSourceWrapper CreateDataSource(ScheduledEmailDataSource model, ScheduledEmailDataSourceEmailType connectionModel) { try diff --git a/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSource.cs b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSource.cs new file mode 100644 index 0000000000..3d9b41aebc --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSource.cs @@ -0,0 +1,47 @@ +//****************************************************************************************************** +// TriggeredDataSource.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Xml.Linq; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class TriggeredDataSource : ITriggeredDataSource + { + public string Name { get; } + private ITriggeredDataSource UnderlyingDataSource { get; } + + public TriggeredDataSource(string name, ITriggeredDataSource dataSource) + { + Name = name; + UnderlyingDataSource = dataSource; + } + + public void Configure(Action configurator) => + UnderlyingDataSource.Configure(configurator); + + public XElement Process(Event evt) => + UnderlyingDataSource.Process(evt); + } +} diff --git a/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceDefinition.cs b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceDefinition.cs new file mode 100644 index 0000000000..2b4c3b7bbb --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceDefinition.cs @@ -0,0 +1,58 @@ +//****************************************************************************************************** +// TriggeredDataSourceDefinition.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using log4net; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class TriggeredDataSourceDefinition + { + public EmailType EmailType { get; set; } + public TriggeredEmailDataSourceEmailType Mapping { get; set; } + public TriggeredEmailDataSource DataSource { get; set; } + + public DataSourceResponse CreateAndProcess(TriggeredDataSourceFactory factory, Event evt) + { + DataSourceResponse response = new DataSourceResponse() { Model = DataSource }; + + try + { + TriggeredDataSource dataSource = factory.CreateDataSource(this); + response.Created = true; + response.Data = dataSource.Process(evt); + response.Success = true; + } + catch (Exception ex) + { + Log.Debug(ex.Message, ex); + response.Exception = ex; + } + + return response; + } + + private static readonly ILog Log = LogManager.GetLogger(typeof(TriggeredDataSourceDefinition)); + } +} diff --git a/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceFactory.cs b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceFactory.cs new file mode 100644 index 0000000000..250dd136a5 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredDataSourceFactory.cs @@ -0,0 +1,78 @@ +//****************************************************************************************************** +// TriggeredDataSourceFactory.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using GSF.Data; +using GSF.Data.Model; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class TriggeredDataSourceFactory + { + private Func ConnectionFactory { get; } + + public TriggeredDataSourceFactory(Func connectionFactory) => + ConnectionFactory = connectionFactory; + + public List LoadDataSourceDefinitions(EmailType emailType) + { + using (AdoDataConnection connection = ConnectionFactory()) + { + TableOperations dataSourceTable = new TableOperations(connection); + TableOperations dataSourceEmailTypeTable = new TableOperations(connection); + + TriggeredDataSourceDefinition ToDefinition(TriggeredEmailDataSourceEmailType mapping) => new TriggeredDataSourceDefinition() + { + EmailType = emailType, + Mapping = mapping, + DataSource = dataSourceTable.QueryRecordWhere("ID = {0}", mapping.TriggeredEmailDataSourceID) + }; + + return dataSourceEmailTypeTable + .QueryRecordsWhere("EmailTypeID = {0}", emailType.ID) + .Select(ToDefinition) + .Where(definition => !(definition.DataSource is null)) + .ToList(); + } + } + + public TriggeredDataSource CreateDataSource(TriggeredDataSourceDefinition definition) + { + string assemblyName = definition.DataSource.AssemblyName; + string typeName = definition.DataSource.TypeName; + PluginFactory pluginFactory = new PluginFactory(); + Type pluginType = pluginFactory.GetPluginType(assemblyName, typeName); + Type dbFactoryType = typeof(Func); + ConstructorInfo constructor = pluginType.GetConstructor(new[] { dbFactoryType }); + object[] parameters = (constructor is null) ? Array.Empty() : new object[] { ConnectionFactory }; + ITriggeredDataSource dataSource = pluginFactory.Create(assemblyName, typeName, parameters); + ConfigurationLoader configurationLoader = new ConfigurationLoader(definition.Mapping.ID, ConnectionFactory); + dataSource.Configure(configurationLoader.Configure); + return new TriggeredDataSource(definition.DataSource.Name, dataSource); + } + } +} diff --git a/Source/Libraries/FaultData/FaultData.csproj b/Source/Libraries/FaultData/FaultData.csproj index 55f906d629..91195e6b26 100644 --- a/Source/Libraries/FaultData/FaultData.csproj +++ b/Source/Libraries/FaultData/FaultData.csproj @@ -175,7 +175,11 @@ + + + + diff --git a/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs b/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs index 1c9b66420f..b5e4e6c8d7 100644 --- a/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs +++ b/Source/Libraries/openXDA.Model/Emails/EmailDataSource.cs @@ -28,7 +28,7 @@ namespace openXDA.Model { [KnownType(typeof(ScheduledEmailDataSource))] [KnownType(typeof(TriggeredEmailDataSource))] - public class EmailDataSource + public abstract class EmailDataSource { [PrimaryKey(true)] public int ID { get; set; } From 5e47b2fc473ca1542e0d605d130b9295879b16bb Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Wed, 15 May 2024 15:26:29 -0400 Subject: [PATCH 06/12] Extract ScheduledEmailDataSource functions into their own classes --- .../Controllers/WebAPI/EmailController.cs | 10 ++- .../DataWriters/Emails/EmailService.cs | 62 +-------------- .../DataWriters/Emails/ScheduledDataSource.cs | 47 +++++++++++ .../Emails/ScheduledDataSourceDefinition.cs | 58 ++++++++++++++ .../Emails/ScheduledDataSourceFactory.cs | 78 +++++++++++++++++++ Source/Libraries/FaultData/FaultData.csproj | 3 + 6 files changed, 197 insertions(+), 61 deletions(-) create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSource.cs create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceDefinition.cs create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceFactory.cs diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index 4e5771393b..ecce028f1d 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -262,17 +262,21 @@ public IHttpActionResult TestDataSource(int emailID, int eventID) [Route("testReportData/{reportID:int}/{current}"), HttpGet] public IHttpActionResult TestReportDataSource(int reportID, string current) { - EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(CreateDbConnection); using (AdoDataConnection connection = CreateDbConnection()) { ScheduledEmailType report = new TableOperations(connection).QueryRecordWhere("ID = {0}", reportID); - List dataSourceResponses = new List(); if (!DateTime.TryParse(current, out DateTime xdaNow)) xdaNow = DateTime.UtcNow; - emailService.LoadDataSources(report,xdaNow, dataSourceResponses); + List definitions = factory.LoadDataSourceDefinitions(report); + + List dataSourceResponses = definitions + .Select(definition => definition.CreateAndProcess(factory, xdaNow)) + .ToList(); + return Ok(dataSourceResponses); } } diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 9d239b3f77..0c8c72e2d0 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -29,7 +29,6 @@ using System.Linq; using System.Net; using System.Net.Mail; -using System.Reflection; using System.Security; using System.Text; using System.Xml.Linq; @@ -202,7 +201,10 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient try { - LoadDataSources(email, xdaNow, response.DataSources); + ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(ConnectionFactory); + List definitions = factory.LoadDataSourceDefinitions(email); + IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, xdaNow)); + response.DataSources.AddRange(dataSourceResponses); Settings settings = new Settings(Configure); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); @@ -219,39 +221,6 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient } } - public void LoadDataSources(ScheduledEmailType email, DateTime now, List responses) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - /* Load All DataSources */ - TableOperations dataSourceTable = new TableOperations(connection); - TableOperations dataSourceEmailTypeTable = new TableOperations(connection); - IEnumerable dataSourceMappings = dataSourceEmailTypeTable.QueryRecordsWhere("ScheduledEmailTypeID = {0}", email.ID); - - foreach (ScheduledEmailDataSourceEmailType dataSourceMapping in dataSourceMappings) - { - ScheduledEmailDataSource dataSource = dataSourceTable.QueryRecordWhere("ID = {0}", dataSourceMapping.ScheduledEmailDataSourceID); - DataSourceWrapper wrapper = CreateDataSource(dataSource, dataSourceMapping); - - Exception ex = null; - XElement data = wrapper?.TryProcess(now); - if (wrapper is null) - ex = new Exception("Failed to create data source"); - - DataSourceResponse response = new DataSourceResponse(); - response.Model = dataSource; - response.Created = !(wrapper is null); - response.Success = !(data is null); - response.Data = data; - response.Exception = ex; - responses.Add(response); - } - - if (responses.Any(response => !response.Created)) - Log.Error("Failed to create one or more data sources for scheduled email; check debug logs for details"); - } - } - public List GetRecipients(EmailType emailType, List eventIDs) { List assetGroups = GetAssetGroups(eventIDs) @@ -424,29 +393,6 @@ public XDocument ApplyTemplate(EmailTypeBase emailType, string templateData) return htmlDocument; } - private DataSourceWrapper CreateDataSource(ScheduledEmailDataSource model, ScheduledEmailDataSourceEmailType connectionModel) - { - try - { - string assemblyName = model.AssemblyName; - string typeName = model.TypeName; - PluginFactory pluginFactory = new PluginFactory(); - Type pluginType = pluginFactory.GetPluginType(assemblyName, typeName); - Type dbFactoryType = typeof(Func); - ConstructorInfo constructor = pluginType.GetConstructor(new[] { dbFactoryType }); - object[] parameters = (constructor is null) ? Array.Empty() : new object[] { ConnectionFactory }; - IScheduledDataSource dataSource = pluginFactory.Create(assemblyName, typeName, parameters); - ConfigurationLoader configurationLoader = new ConfigurationLoader(connectionModel.ID, ConnectionFactory); - dataSource.Configure(configurationLoader.Configure); - return new DataSourceWrapper(model.Name, null, dataSource); - } - catch (Exception ex) - { - Log.Debug($"Failed to create ITriggeredDataSource of type {model.TypeName}", ex); - return new DataSourceWrapper(model.Name, null); - } - } - private List GetAssetGroups(List eventIDs) { if (eventIDs.Count == 0) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSource.cs b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSource.cs new file mode 100644 index 0000000000..7f18027a27 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSource.cs @@ -0,0 +1,47 @@ +//****************************************************************************************************** +// ScheduledDataSource.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Xml.Linq; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class ScheduledDataSource : IScheduledDataSource + { + public string Name { get; } + private IScheduledDataSource UnderlyingDataSource { get; } + + public ScheduledDataSource(string name, IScheduledDataSource dataSource) + { + Name = name; + UnderlyingDataSource = dataSource; + } + + public void Configure(Action configurator) => + UnderlyingDataSource.Configure(configurator); + + public XElement Process(DateTime timeOccurred) => + UnderlyingDataSource.Process(timeOccurred); + } +} diff --git a/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceDefinition.cs b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceDefinition.cs new file mode 100644 index 0000000000..2485f9b7b9 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceDefinition.cs @@ -0,0 +1,58 @@ +//****************************************************************************************************** +// ScheduledDataSourceDefinition.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using log4net; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class ScheduledDataSourceDefinition + { + public ScheduledEmailType EmailType { get; set; } + public ScheduledEmailDataSourceEmailType Mapping { get; set; } + public ScheduledEmailDataSource DataSource { get; set; } + + public DataSourceResponse CreateAndProcess(ScheduledDataSourceFactory factory, DateTime timeOccurred) + { + DataSourceResponse response = new DataSourceResponse() { Model = DataSource }; + + try + { + ScheduledDataSource dataSource = factory.CreateDataSource(this); + response.Created = true; + response.Data = dataSource.Process(timeOccurred); + response.Success = true; + } + catch (Exception ex) + { + Log.Debug(ex.Message, ex); + response.Exception = ex; + } + + return response; + } + + private static readonly ILog Log = LogManager.GetLogger(typeof(ScheduledDataSourceDefinition)); + } +} diff --git a/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceFactory.cs b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceFactory.cs new file mode 100644 index 0000000000..87c8f5ce12 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledDataSourceFactory.cs @@ -0,0 +1,78 @@ +//****************************************************************************************************** +// ScheduledDataSourceFactory.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using GSF.Data; +using GSF.Data.Model; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class ScheduledDataSourceFactory + { + private Func ConnectionFactory { get; } + + public ScheduledDataSourceFactory(Func connectionFactory) => + ConnectionFactory = connectionFactory; + + public List LoadDataSourceDefinitions(ScheduledEmailType emailType) + { + using (AdoDataConnection connection = ConnectionFactory()) + { + TableOperations dataSourceTable = new TableOperations(connection); + TableOperations dataSourceEmailTypeTable = new TableOperations(connection); + + ScheduledDataSourceDefinition ToDefinition(ScheduledEmailDataSourceEmailType mapping) => new ScheduledDataSourceDefinition() + { + EmailType = emailType, + Mapping = mapping, + DataSource = dataSourceTable.QueryRecordWhere("ID = {0}", mapping.ScheduledEmailDataSourceID) + }; + + return dataSourceEmailTypeTable + .QueryRecordsWhere("ScheduledEmailTypeID = {0}", emailType.ID) + .Select(ToDefinition) + .Where(definition => !(definition.DataSource is null)) + .ToList(); + } + } + + public ScheduledDataSource CreateDataSource(ScheduledDataSourceDefinition definition) + { + string assemblyName = definition.DataSource.AssemblyName; + string typeName = definition.DataSource.TypeName; + PluginFactory pluginFactory = new PluginFactory(); + Type pluginType = pluginFactory.GetPluginType(assemblyName, typeName); + Type dbFactoryType = typeof(Func); + ConstructorInfo constructor = pluginType.GetConstructor(new[] { dbFactoryType }); + object[] parameters = (constructor is null) ? Array.Empty() : new object[] { ConnectionFactory }; + IScheduledDataSource dataSource = pluginFactory.Create(assemblyName, typeName, parameters); + ConfigurationLoader configurationLoader = new ConfigurationLoader(definition.Mapping.ID, ConnectionFactory); + dataSource.Configure(configurationLoader.Configure); + return new ScheduledDataSource(definition.DataSource.Name, dataSource); + } + } +} diff --git a/Source/Libraries/FaultData/FaultData.csproj b/Source/Libraries/FaultData/FaultData.csproj index 91195e6b26..e277739e39 100644 --- a/Source/Libraries/FaultData/FaultData.csproj +++ b/Source/Libraries/FaultData/FaultData.csproj @@ -177,7 +177,10 @@ + + + From 95efb90b4376a4112568e7738d1e862b39824cd1 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Wed, 15 May 2024 16:32:30 -0400 Subject: [PATCH 07/12] Refactor email template functions to the TemplateProcessor --- .../DataWriters/Emails/EmailService.cs | 172 ++++++------------ .../DataWriters/Emails/TemplateProcessor.cs | 119 ++++++++++++ Source/Libraries/FaultData/FaultData.csproj | 1 + 3 files changed, 171 insertions(+), 121 deletions(-) create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/TemplateProcessor.cs diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 0c8c72e2d0..d32c37abcd 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -30,12 +30,10 @@ using System.Net; using System.Net.Mail; using System.Security; -using System.Text; using System.Xml.Linq; using GSF.Configuration; using GSF.Data; using GSF.Data.Model; -using GSF.Xml; using log4net; using openXDA.Configuration; using openXDA.Model; @@ -155,16 +153,20 @@ private void SendEmail(EmailType email, Event evt, List recipients, Date try { + Settings settings = new Settings(Configure); + TriggeredDataSourceFactory factory = new TriggeredDataSourceFactory(ConnectionFactory); List definitions = factory.LoadDataSourceDefinitions(email); IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, evt)); response.DataSources.AddRange(dataSourceResponses); - Settings settings = new Settings(Configure); + double chartSampleRate = settings.EmailSettings.MinimumChartSamplesPerCycle; + TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); - XDocument htmlDocument = ApplyTemplate(email, templateData.ToString()); - ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); - ApplyImageEmbedTransform(attachments, htmlDocument); + XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); + templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); + templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); + SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); if (eventIDs.Count() > 0) LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); @@ -201,17 +203,20 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient try { + Settings settings = new Settings(Configure); + ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(ConnectionFactory); List definitions = factory.LoadDataSourceDefinitions(email); IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, xdaNow)); response.DataSources.AddRange(dataSourceResponses); - Settings settings = new Settings(Configure); + double chartSampleRate = settings.EmailSettings.MinimumChartSamplesPerCycle; + TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); - XDocument htmlDocument = ApplyTemplate(email, templateData.ToString()); + XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); + templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); + templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); - ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); - ApplyImageEmbedTransform(attachments, htmlDocument); SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); LoadSentEmail(email, xdaNow, recipients, htmlDocument); } @@ -221,6 +226,42 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient } } + public void SendAdminEmail(string subject, string message, List replyToRecipients) + { + Settings settings = new Settings(Configure); + EmailSection emailSettings = settings.EmailSettings; + string smtpServer = emailSettings.SMTPServer; + + if (string.IsNullOrEmpty(smtpServer)) + return; + + using (SmtpClient smtpClient = CreateSmtpClient(smtpServer)) + using (MailMessage emailMessage = new MailMessage()) + { + string username = emailSettings.Username; + SecureString password = emailSettings.SecurePassword; + + if (!string.IsNullOrEmpty(username) && (object)password != null) + smtpClient.Credentials = new NetworkCredential(username, password); + + smtpClient.EnableSsl = emailSettings.EnableSSL; + + string fromAddress = emailSettings.FromAddress; + string toAddress = emailSettings.AdminAddress; + emailMessage.From = new MailAddress(fromAddress); + emailMessage.To.Add(toAddress); + emailMessage.Subject = subject; + emailMessage.Body = message; + + // Add the specified To recipients for the email message + foreach (string replyToRecipient in replyToRecipients) + emailMessage.ReplyToList.Add(replyToRecipient.Trim()); + + // Send the email + smtpClient.Send(emailMessage); + } + } + public List GetRecipients(EmailType emailType, List eventIDs) { List assetGroups = GetAssetGroups(eventIDs) @@ -379,20 +420,6 @@ public List GetRecipients(ScheduledEmailType emailType) } } - public XDocument ApplyTemplate(EmailTypeBase emailType, string templateData) - { - string htmlText = templateData.ApplyXSLTransform(emailType.Template); - - XDocument htmlDocument = XDocument.Parse(htmlText, LoadOptions.PreserveWhitespace); - htmlDocument.TransformAll("format", element => { - object f; - try { f = element.Format(); } - catch { f = ""; } - return f; - }); - return htmlDocument; - } - private List GetAssetGroups(List eventIDs) { if (eventIDs.Count == 0) @@ -444,67 +471,6 @@ private void LoadSentEmail(EmailType email, DateTime now, List recipient } } - public void ApplyChartTransform(List attachments, XDocument htmlDocument, int minSamplesPerCycle = -1) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - htmlDocument.TransformAll("chart", (element, index) => - { - string chartEventID = (string) element.Attribute("eventID") ?? "-1"; - string cid = $"event{chartEventID}_chart{index:00}.png"; - - string stringMinimum = (string) element.Attribute("minimumSamplesPerCycleOverride"); - int passedMinimum = minSamplesPerCycle; - if (!(stringMinimum is null) && !int.TryParse(stringMinimum, out passedMinimum)) - passedMinimum = -1; - - Stream image = ChartGenerator.ConvertToChartImageStream(connection, element, passedMinimum); - Attachment attachment = new Attachment(image, cid); - attachment.ContentId = attachment.Name; - attachments.Add(attachment); - - return new XElement("img", new XAttribute("src", $"cid:{cid}")); - }); - } - } - - public void ApplyImageEmbedTransform(List attachments, XDocument htmlDocument) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - htmlDocument.TransformAll("embed", (element, index) => - { - string cid = $"image{index:00}.jpg"; - - try - { - string base64 = (string)element; - byte[] imageData = Convert.FromBase64String(base64); - MemoryStream stream = new MemoryStream(imageData); - Attachment attachment = new Attachment(stream, cid); - attachment.ContentId = attachment.Name; - attachments.Add(attachment); - return new XElement("img", new XAttribute("src", $"cid:{cid}")); - } - catch (Exception ex) - { - string text = new StringBuilder() - .AppendLine($"Error while loading {cid}:") - .Append(ex.ToString()) - .ToString(); - - object[] content = text - .Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) - .SelectMany(line => new object[] { new XElement("br"), new XText(line) }) - .Skip(1) - .ToArray(); - - return new XElement("div", content); - } - }); - } - } - private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath = null) { EmailSection emailSettings = settings.EmailSettings; @@ -578,42 +544,6 @@ private void WriteEmailToFile(string datafolder, MailMessage mail) fileWriter.Write(mail.Body); } - public void SendAdminEmail(string subject, string message, List replyToRecipients) - { - Settings settings = new Settings(Configure); - EmailSection emailSettings = settings.EmailSettings; - string smtpServer = emailSettings.SMTPServer; - - if (string.IsNullOrEmpty(smtpServer)) - return; - - using (SmtpClient smtpClient = CreateSmtpClient(smtpServer)) - using (MailMessage emailMessage = new MailMessage()) - { - string username = emailSettings.Username; - SecureString password = emailSettings.SecurePassword; - - if (!string.IsNullOrEmpty(username) && (object)password != null) - smtpClient.Credentials = new NetworkCredential(username, password); - - smtpClient.EnableSsl = emailSettings.EnableSSL; - - string fromAddress = emailSettings.FromAddress; - string toAddress = emailSettings.AdminAddress; - emailMessage.From = new MailAddress(fromAddress); - emailMessage.To.Add(toAddress); - emailMessage.Subject = subject; - emailMessage.Body = message; - - // Add the specified To recipients for the email message - foreach (string replyToRecipient in replyToRecipients) - emailMessage.ReplyToList.Add(replyToRecipient.Trim()); - - // Send the email - smtpClient.Send(emailMessage); - } - } - private string GetSubject(XDocument htmlDocument, EmailTypeBase emailType) { string subject = (string)((string)htmlDocument diff --git a/Source/Libraries/FaultData/DataWriters/Emails/TemplateProcessor.cs b/Source/Libraries/FaultData/DataWriters/Emails/TemplateProcessor.cs new file mode 100644 index 0000000000..3adc390d29 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/TemplateProcessor.cs @@ -0,0 +1,119 @@ +//****************************************************************************************************** +// TemplateProcessor.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Xml.Linq; +using GSF.Data; +using GSF.Xml; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class TemplateProcessor + { + private Func ConnectionFactory { get; } + + public TemplateProcessor(Func connectionFactory) => + ConnectionFactory = connectionFactory; + + public XDocument ApplyTemplate(EmailTypeBase emailType, string templateData) + { + string htmlText = templateData.ApplyXSLTransform(emailType.Template); + + XDocument htmlDocument = XDocument.Parse(htmlText, LoadOptions.PreserveWhitespace); + + htmlDocument.TransformAll("format", element => { + try { return element.Format(); } + catch { return string.Empty; } + }); + + return htmlDocument; + } + + public void ApplyChartTransform(List attachments, XDocument htmlDocument, int minSamplesPerCycle = -1) + { + using (AdoDataConnection connection = ConnectionFactory()) + { + htmlDocument.TransformAll("chart", (element, index) => + { + string chartEventID = (string)element.Attribute("eventID") ?? "-1"; + string cid = $"event{chartEventID}_chart{index:00}.png"; + + string stringMinimum = (string)element.Attribute("minimumSamplesPerCycleOverride"); + int passedMinimum = minSamplesPerCycle; + if (!(stringMinimum is null) && !int.TryParse(stringMinimum, out passedMinimum)) + passedMinimum = -1; + + Stream image = ChartGenerator.ConvertToChartImageStream(connection, element, passedMinimum); + Attachment attachment = new Attachment(image, cid); + attachment.ContentId = attachment.Name; + attachments.Add(attachment); + + return new XElement("img", new XAttribute("src", $"cid:{cid}")); + }); + } + } + + public void ApplyImageEmbedTransform(List attachments, XDocument htmlDocument) + { + using (AdoDataConnection connection = ConnectionFactory()) + { + htmlDocument.TransformAll("embed", (element, index) => + { + string cid = $"image{index:00}.jpg"; + + try + { + string base64 = (string)element; + byte[] imageData = Convert.FromBase64String(base64); + MemoryStream stream = new MemoryStream(imageData); + Attachment attachment = new Attachment(stream, cid); + attachment.ContentId = attachment.Name; + attachments.Add(attachment); + return new XElement("img", new XAttribute("src", $"cid:{cid}")); + } + catch (Exception ex) + { + string text = new StringBuilder() + .AppendLine($"Error while loading {cid}:") + .Append(ex.ToString()) + .ToString(); + + object[] content = text + .Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) + .SelectMany(line => new object[] { new XElement("br"), new XText(line) }) + .Skip(1) + .ToArray(); + + return new XElement("div", content); + } + }); + } + } + } +} diff --git a/Source/Libraries/FaultData/FaultData.csproj b/Source/Libraries/FaultData/FaultData.csproj index e277739e39..94831303a5 100644 --- a/Source/Libraries/FaultData/FaultData.csproj +++ b/Source/Libraries/FaultData/FaultData.csproj @@ -178,6 +178,7 @@ + From ff079f1dbb6f731204d0d085a838196164b1098e Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Wed, 15 May 2024 16:36:12 -0400 Subject: [PATCH 08/12] Rearrange EmailService functions and fix issue where triggered emails could fail to be loaded into the database --- .../DataWriters/Emails/EmailService.cs | 104 +++++++++--------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index d32c37abcd..bbdfed99de 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -168,8 +168,7 @@ private void SendEmail(EmailType email, Event evt, List recipients, Date templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); - if (eventIDs.Count() > 0) - LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); + LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); } finally { @@ -450,27 +449,6 @@ private List GetAssetGroups(List eventIDs) } } - private void LoadSentEmail(EmailType email, DateTime now, List recipients, XDocument htmlDocument, List eventIDs) - { - int sentEmailID = LoadSentEmail(email, now, recipients, htmlDocument); - - using (AdoDataConnection connection = ConnectionFactory()) - { - TableOperations eventSentEmailTable = new TableOperations(connection); - - foreach (int eventID in eventIDs) - { - if (eventSentEmailTable.QueryRecordCountWhere("EventID = {0} AND SentEmailID = {1}", eventID, sentEmailID) > 0) - continue; - - EventSentEmail eventSentEmail = new EventSentEmail(); - eventSentEmail.EventID = eventID; - eventSentEmail.SentEmailID = sentEmailID; - eventSentEmailTable.AddNewRecord(eventSentEmail); - } - } - } - private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath = null) { EmailSection emailSettings = settings.EmailSettings; @@ -530,6 +508,47 @@ private void SendEmail(List recipients, XDocument htmlDocument, List recipients, XDocument htmlDocument, List eventIDs) + { + int sentEmailID = LoadSentEmail(email, now, recipients, htmlDocument); + + if (eventIDs.Count == 0) + return; + + using (AdoDataConnection connection = ConnectionFactory()) + { + TableOperations eventSentEmailTable = new TableOperations(connection); + + foreach (int eventID in eventIDs) + { + if (eventSentEmailTable.QueryRecordCountWhere("EventID = {0} AND SentEmailID = {1}", eventID, sentEmailID) > 0) + continue; + + EventSentEmail eventSentEmail = new EventSentEmail(); + eventSentEmail.EventID = eventID; + eventSentEmail.SentEmailID = sentEmailID; + eventSentEmailTable.AddNewRecord(eventSentEmail); + } + } + } + + private int LoadSentEmail(EmailTypeBase email, DateTime now, List recipients, XDocument htmlDocument) + { + using (AdoDataConnection connection = ConnectionFactory()) + { + SentEmail sentEmail = new SentEmail(); + sentEmail.EmailTypeID = email.ID; + sentEmail.TimeSent = now; + sentEmail.ToLine = string.Join(";", recipients.Select(recipient => recipient.Trim())); + sentEmail.Subject = GetSubject(htmlDocument, email); + sentEmail.Message = GetBody(htmlDocument); + + TableOperations sentEmailTable = new TableOperations(connection); + sentEmailTable.AddNewRecord(sentEmail); + return connection.ExecuteScalar("SELECT @@IDENTITY"); + } + } + private void WriteEmailToFile(string datafolder, MailMessage mail) { if (string.IsNullOrEmpty(datafolder)) @@ -544,21 +563,6 @@ private void WriteEmailToFile(string datafolder, MailMessage mail) fileWriter.Write(mail.Body); } - private string GetSubject(XDocument htmlDocument, EmailTypeBase emailType) - { - string subject = (string)((string)htmlDocument - .Descendants("title") - .FirstOrDefault()); - - return (subject ?? emailType.Name).Trim(); - } - - private string GetBody(XDocument htmlDocument) => htmlDocument - .ToString(SaveOptions.DisableFormatting) - .Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">"); - private SmtpClient CreateSmtpClient(string smtpServer) { string[] smtpServerParts = smtpServer.Split(':'); @@ -570,23 +574,21 @@ private SmtpClient CreateSmtpClient(string smtpServer) return new SmtpClient(host); } - private int LoadSentEmail(EmailTypeBase email, DateTime now, List recipients, XDocument htmlDocument) + private string GetSubject(XDocument htmlDocument, EmailTypeBase emailType) { - using (AdoDataConnection connection = ConnectionFactory()) - { - SentEmail sentEmail = new SentEmail(); - sentEmail.EmailTypeID = email.ID; - sentEmail.TimeSent = now; - sentEmail.ToLine = string.Join(";", recipients.Select(recipient => recipient.Trim())); - sentEmail.Subject = GetSubject(htmlDocument, email); - sentEmail.Message = GetBody(htmlDocument); + string subject = (string)((string)htmlDocument + .Descendants("title") + .FirstOrDefault()); - TableOperations sentEmailTable = new TableOperations(connection); - sentEmailTable.AddNewRecord(sentEmail); - return connection.ExecuteScalar("SELECT @@IDENTITY"); - } + return (subject ?? emailType.Name).Trim(); } + private string GetBody(XDocument htmlDocument) => htmlDocument + .ToString(SaveOptions.DisableFormatting) + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">"); + #endregion #region [ Static ] From 0a03b02935cbbf280347516f08ffa692ca9bd4c1 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Thu, 16 May 2024 10:20:56 -0400 Subject: [PATCH 09/12] Extract triggered email functions into their own subclass --- .../Controllers/WebAPI/EmailController.cs | 4 +- .../DataWriters/Emails/EmailService.cs | 288 ++++-------------- .../Emails/TriggeredEmailService.cs | 214 +++++++++++++ Source/Libraries/FaultData/FaultData.csproj | 1 + .../Types/Email/EventEmailNode.cs | 4 +- 5 files changed, 280 insertions(+), 231 deletions(-) create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/TriggeredEmailService.cs diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index ecce028f1d..9c8720b734 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -122,7 +122,7 @@ public IHttpActionResult SendTestEmail(int emailID, string userID, int eventID) { Settings settings = new Settings(GetConfigurator()); EventEmailSection eventEmailSettings = settings.EventEmailSettings; - EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + TriggeredEmailService emailService = new TriggeredEmailService(CreateDbConnection, GetConfigurator()); using (AdoDataConnection connection = CreateDbConnection()) { @@ -163,7 +163,7 @@ IHttpActionResult RunTest() { Settings settings = new Settings(GetConfigurator()); EventEmailSection eventEmailSettings = settings.EventEmailSettings; - EmailService emailService = new EmailService(CreateDbConnection, GetConfigurator()); + TriggeredEmailService emailService = new TriggeredEmailService(CreateDbConnection, GetConfigurator()); using (AdoDataConnection connection = CreateDbConnection()) { diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index bbdfed99de..63692c387c 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -111,88 +111,91 @@ public EmailService(Func connectionFactory, Action co #region [ Properties ] - private Func ConnectionFactory { get; } + protected Func ConnectionFactory { get; } private Action Configure { get; } #endregion #region [ Methods ] - public bool SendEmail(EmailType email, List eventIDs, Event evt, DateTime xdaNow) + public bool SendScheduledEmail(ScheduledEmailType email, DateTime xdaNow) { - if (eventIDs.Count == 0) - return false; - - List recipients = GetRecipients(email, eventIDs); + List recipients = GetRecipients(email); - if (recipients.Count == 0 && String.IsNullOrEmpty(email.FilePath)) + if (recipients.Count == 0 && string.IsNullOrEmpty(email.FilePath)) return false; - SendEmail(email, evt, recipients, xdaNow, eventIDs, true); + SendScheduledEmail(email, recipients, true, xdaNow); return true; } - public void SendEmail(EmailType email, Event evt, List recipients, bool saveToFile, out EmailResponse response) => - SendEmail(email, evt, recipients, new DateTime(), new List(), saveToFile, out response); - - public void SendEmail(EmailType email, Event evt, List recipients, out EmailResponse response) => - SendEmail(email, evt, recipients, new DateTime(), new List(), false, out response); + public void SendScheduledEmail(ScheduledEmailType email, List recipients, out EmailResponse response, DateTime xdaNow) => + SendScheduledEmail(email, recipients, false, out response, xdaNow); - public void SendEmail(EmailType email, Event evt, List recipients) => - SendEmail(email, evt, recipients, new DateTime(), new List(), false, out EmailResponse _); + public void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => + SendScheduledEmail(email, recipients, saveToFile, out EmailResponse _, xdaNow); - private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile) => - SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, out EmailResponse _); + public void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, EmailResponse email, string filePath = null) => + SendEmail(recipients, htmlDocument, attachments, emailType, QuerySettings(), email, filePath); - private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, out EmailResponse response) + public void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, EmailSection settings, EmailResponse email, string filePath = null) { - List attachments = new List(); + string smtpServer = settings.SMTPServer; - response = new EmailResponse(); + email.Body = GetBody(htmlDocument); + email.Subject = GetSubject(htmlDocument, emailType); - try + if (string.IsNullOrEmpty(smtpServer)) + return; + + using (SmtpClient smtpClient = CreateSmtpClient(smtpServer)) + using (MailMessage emailMessage = new MailMessage()) { - Settings settings = new Settings(Configure); + string username = settings.Username; + SecureString password = settings.SecurePassword; - TriggeredDataSourceFactory factory = new TriggeredDataSourceFactory(ConnectionFactory); - List definitions = factory.LoadDataSourceDefinitions(email); - IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, evt)); - response.DataSources.AddRange(dataSourceResponses); + if (!string.IsNullOrEmpty(username) && (object)password != null) + smtpClient.Credentials = new NetworkCredential(username, password); - double chartSampleRate = settings.EmailSettings.MinimumChartSamplesPerCycle; - TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); - XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); - XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); - templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); - templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); + smtpClient.EnableSsl = settings.EnableSSL; - SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); - LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); - } - finally - { - attachments?.ForEach(attachment => attachment.Dispose()); - } - } + string fromAddress = settings.FromAddress; + emailMessage.From = new MailAddress(fromAddress); + emailMessage.Subject = email.Subject; + emailMessage.Body = email.Body; + emailMessage.IsBodyHtml = true; - public bool SendScheduledEmail(ScheduledEmailType email, DateTime xdaNow) - { - List recipients = GetRecipients(email); + if (recipients.Count == 0) + { + WriteEmailToFile(filePath, emailMessage); + return; + } - if (recipients.Count == 0 && string.IsNullOrEmpty(email.FilePath)) - return false; + string blindCopyAddress = settings.BlindCopyAddress; + string recipientList = string.Join(",", recipients.Select(recipient => recipient.Trim())); - SendScheduledEmail(email, recipients, true, xdaNow); + if (string.IsNullOrEmpty(blindCopyAddress)) + { + emailMessage.To.Add(recipientList); + } + else + { + emailMessage.To.Add(blindCopyAddress); + emailMessage.Bcc.Add(recipientList); + } - return true; - } + // Create the image attachment for the email message + foreach (Attachment attachment in attachments) + emailMessage.Attachments.Add(attachment); - public void SendScheduledEmail(ScheduledEmailType email, List recipients, out EmailResponse response, DateTime xdaNow) => - SendScheduledEmail(email, recipients, false, out response, xdaNow); + // Send the email + smtpClient.Send(emailMessage); - public void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => - SendScheduledEmail(email, recipients, saveToFile, out EmailResponse _, xdaNow); + //Write the email to a File + WriteEmailToFile(filePath, emailMessage); + } + } private void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, out EmailResponse response, DateTime xdaNow) { @@ -202,18 +205,18 @@ private void SendScheduledEmail(ScheduledEmailType email, List recipient try { - Settings settings = new Settings(Configure); + EmailSection settings = QuerySettings(); ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(ConnectionFactory); List definitions = factory.LoadDataSourceDefinitions(email); IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, xdaNow)); response.DataSources.AddRange(dataSourceResponses); - double chartSampleRate = settings.EmailSettings.MinimumChartSamplesPerCycle; + double chartSampleRate = settings.MinimumChartSamplesPerCycle; TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); - templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.EmailSettings.MinimumChartSamplesPerCycle); + templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.MinimumChartSamplesPerCycle); templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); @@ -261,64 +264,6 @@ public void SendAdminEmail(string subject, string message, List replyToR } } - public List GetRecipients(EmailType emailType, List eventIDs) - { - List assetGroups = GetAssetGroups(eventIDs) - .Select(item => item.ID) - .ToList(); - - if (assetGroups.Count == 0) - return new List(); - - string assetGroupFilter = string.Join(",", assetGroups); - - string emailAddressQuery; - Func processor; - - if (!emailType.SMS) - { - - emailAddressQuery = - $"SELECT DISTINCT UserAccount.Email AS Email " + - $"FROM " + - $" UserAccountEmailType JOIN " + - $" UserAccount ON UserAccountEmailType.UserAccountID = UserAccount.ID " + - $"WHERE " + - $" UserAccountEmailType.EmailTypeID = {{0}} AND " + - $" UserAccount.EmailConfirmed <> 0 AND " + - $" UserAccount.Approved <> 0 AND " + - $" UserAccountEmailType.AssetGroupID IN ({assetGroupFilter})"; - - processor = row => row.ConvertField("Email"); - } - else - { - emailAddressQuery = - $"SELECT DISTINCT UserAccount.Phone AS Phone, CellCarrier.Transform as Transform " + - $"FROM " + - $" UserAccountEmailType JOIN " + - $" UserAccount ON UserAccountEmailType.UserAccountID = UserAccount.ID LEFT JOIN " + - $" UserAccountCarrier ON UserAccountCarrier.UserAccountID = UserAccount.ID LEFT JOIN " + - $" CellCarrier ON UserAccountCarrier.CarrierID = CellCarrier.ID " + - $"WHERE " + - $" UserAccountEmailType.EmailTypeID = {{0}} AND " + - $" UserAccount.PhoneConfirmed <> 0 AND " + - $" UserAccount.Approved <> 0 AND " + - $" UserAccountEmailType.AssetGroupID IN ({assetGroupFilter})"; - - processor = row => string.Format(row.ConvertField("Transform"), row.ConvertField("Phone")); - } - - using (AdoDataConnection connection = ConnectionFactory()) - using (DataTable emailAddressTable = connection.RetrieveData(emailAddressQuery, emailType.ID)) - { - return emailAddressTable - .Select() - .Select(processor) - .ToList(); - } - } - public List GetRecipients(EmailType emailType) { string emailAddressQuery; @@ -419,120 +364,7 @@ public List GetRecipients(ScheduledEmailType emailType) } } - private List GetAssetGroups(List eventIDs) - { - if (eventIDs.Count == 0) - return new List(); - - string query = - $"SELECT DISTINCT AssetGroup.* " + - $"FROM " + - $" AssetGroup LEFT OUTER JOIN " + - $" MeterAssetGroup ON MeterAssetGroup.AssetGroupID = AssetGroup.ID LEFT OUTER JOIN " + - $" AssetAssetGroup ON AssetAssetGroup.AssetGroupID = AssetGroup.ID JOIN " + - $" Event ON " + - $" Event.ID IN ({string.Join(",", eventIDs)}) AND " + - $" (" + - $" Event.MeterID = MeterAssetGroup.MeterID OR " + - $" Event.AssetID = AssetAssetGroup.AssetID " + - $" )"; - - using (AdoDataConnection connection = ConnectionFactory()) - using (DataTable table = connection.RetrieveData(query)) - { - TableOperations assetGroupTable = new TableOperations(connection); - - return table - .AsEnumerable() - .Select(assetGroupTable.LoadRecord) - .ToList(); - } - } - - private void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, Settings settings, EmailResponse email, string filePath = null) - { - EmailSection emailSettings = settings.EmailSettings; - string smtpServer = emailSettings.SMTPServer; - - email.Body = GetBody(htmlDocument); - email.Subject = GetSubject(htmlDocument, emailType); - - if (string.IsNullOrEmpty(smtpServer)) - return; - - using (SmtpClient smtpClient = CreateSmtpClient(smtpServer)) - using (MailMessage emailMessage = new MailMessage()) - { - string username = emailSettings.Username; - SecureString password = emailSettings.SecurePassword; - - if (!string.IsNullOrEmpty(username) && (object)password != null) - smtpClient.Credentials = new NetworkCredential(username, password); - - smtpClient.EnableSsl = emailSettings.EnableSSL; - - string fromAddress = emailSettings.FromAddress; - emailMessage.From = new MailAddress(fromAddress); - emailMessage.Subject = email.Subject; - emailMessage.Body = email.Body; - emailMessage.IsBodyHtml = true; - - if (recipients.Count == 0) - { - WriteEmailToFile(filePath, emailMessage); - return; - } - - string blindCopyAddress = emailSettings.BlindCopyAddress; - string recipientList = string.Join(",", recipients.Select(recipient => recipient.Trim())); - - if (string.IsNullOrEmpty(blindCopyAddress)) - { - emailMessage.To.Add(recipientList); - } - else - { - emailMessage.To.Add(blindCopyAddress); - emailMessage.Bcc.Add(recipientList); - } - - // Create the image attachment for the email message - foreach (Attachment attachment in attachments) - emailMessage.Attachments.Add(attachment); - - // Send the email - smtpClient.Send(emailMessage); - - //Write the email to a File - WriteEmailToFile(filePath, emailMessage); - } - } - - private void LoadSentEmail(EmailType email, DateTime now, List recipients, XDocument htmlDocument, List eventIDs) - { - int sentEmailID = LoadSentEmail(email, now, recipients, htmlDocument); - - if (eventIDs.Count == 0) - return; - - using (AdoDataConnection connection = ConnectionFactory()) - { - TableOperations eventSentEmailTable = new TableOperations(connection); - - foreach (int eventID in eventIDs) - { - if (eventSentEmailTable.QueryRecordCountWhere("EventID = {0} AND SentEmailID = {1}", eventID, sentEmailID) > 0) - continue; - - EventSentEmail eventSentEmail = new EventSentEmail(); - eventSentEmail.EventID = eventID; - eventSentEmail.SentEmailID = sentEmailID; - eventSentEmailTable.AddNewRecord(eventSentEmail); - } - } - } - - private int LoadSentEmail(EmailTypeBase email, DateTime now, List recipients, XDocument htmlDocument) + protected int LoadSentEmail(EmailTypeBase email, DateTime now, List recipients, XDocument htmlDocument) { using (AdoDataConnection connection = ConnectionFactory()) { @@ -549,6 +381,8 @@ private int LoadSentEmail(EmailTypeBase email, DateTime now, List recipi } } + protected EmailSection QuerySettings() => new Settings(Configure).EmailSettings; + private void WriteEmailToFile(string datafolder, MailMessage mail) { if (string.IsNullOrEmpty(datafolder)) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/TriggeredEmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredEmailService.cs new file mode 100644 index 0000000000..110e2991da --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/TriggeredEmailService.cs @@ -0,0 +1,214 @@ +//****************************************************************************************************** +// TriggeredEmailService.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/15/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net.Mail; +using System.Xml.Linq; +using GSF.Data; +using GSF.Data.Model; +using openXDA.Configuration; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class TriggeredEmailService : EmailService + { + public TriggeredEmailService(Func connectionFactory, Action configure) + : base(connectionFactory, configure) + { + } + + public bool SendEmail(EmailType email, List eventIDs, Event evt, DateTime xdaNow) + { + if (eventIDs.Count == 0) + return false; + + List recipients = GetRecipients(email, eventIDs); + + if (recipients.Count == 0 && String.IsNullOrEmpty(email.FilePath)) + return false; + + SendEmail(email, evt, recipients, xdaNow, eventIDs, true); + + return true; + } + + public void SendEmail(EmailType email, Event evt, List recipients, bool saveToFile, out EmailResponse response) => + SendEmail(email, evt, recipients, new DateTime(), new List(), saveToFile, out response); + + public void SendEmail(EmailType email, Event evt, List recipients, out EmailResponse response) => + SendEmail(email, evt, recipients, new DateTime(), new List(), false, out response); + + public void SendEmail(EmailType email, Event evt, List recipients) => + SendEmail(email, evt, recipients, new DateTime(), new List(), false, out EmailResponse _); + + public List GetRecipients(EmailType emailType, List eventIDs) + { + List assetGroups = GetAssetGroups(eventIDs) + .Select(item => item.ID) + .ToList(); + + if (assetGroups.Count == 0) + return new List(); + + string assetGroupFilter = string.Join(",", assetGroups); + + string emailAddressQuery; + Func processor; + + if (!emailType.SMS) + { + + emailAddressQuery = + $"SELECT DISTINCT UserAccount.Email AS Email " + + $"FROM " + + $" UserAccountEmailType JOIN " + + $" UserAccount ON UserAccountEmailType.UserAccountID = UserAccount.ID " + + $"WHERE " + + $" UserAccountEmailType.EmailTypeID = {{0}} AND " + + $" UserAccount.EmailConfirmed <> 0 AND " + + $" UserAccount.Approved <> 0 AND " + + $" UserAccountEmailType.AssetGroupID IN ({assetGroupFilter})"; + + processor = row => row.ConvertField("Email"); + } + else + { + emailAddressQuery = + $"SELECT DISTINCT UserAccount.Phone AS Phone, CellCarrier.Transform as Transform " + + $"FROM " + + $" UserAccountEmailType JOIN " + + $" UserAccount ON UserAccountEmailType.UserAccountID = UserAccount.ID LEFT JOIN " + + $" UserAccountCarrier ON UserAccountCarrier.UserAccountID = UserAccount.ID LEFT JOIN " + + $" CellCarrier ON UserAccountCarrier.CarrierID = CellCarrier.ID " + + $"WHERE " + + $" UserAccountEmailType.EmailTypeID = {{0}} AND " + + $" UserAccount.PhoneConfirmed <> 0 AND " + + $" UserAccount.Approved <> 0 AND " + + $" UserAccountEmailType.AssetGroupID IN ({assetGroupFilter})"; + + processor = row => string.Format(row.ConvertField("Transform"), row.ConvertField("Phone")); + } + + using (AdoDataConnection connection = ConnectionFactory()) + using (DataTable emailAddressTable = connection.RetrieveData(emailAddressQuery, emailType.ID)) + { + return emailAddressTable + .Select() + .Select(processor) + .ToList(); + } + } + + private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile) => + SendEmail(email, evt, recipients, xdaNow, eventIDs, saveToFile, out EmailResponse _); + + private void SendEmail(EmailType email, Event evt, List recipients, DateTime xdaNow, List eventIDs, bool saveToFile, out EmailResponse response) + { + List attachments = new List(); + + response = new EmailResponse(); + + try + { + EmailSection settings = QuerySettings(); + + TriggeredDataSourceFactory factory = new TriggeredDataSourceFactory(ConnectionFactory); + List definitions = factory.LoadDataSourceDefinitions(email); + IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, evt)); + response.DataSources.AddRange(dataSourceResponses); + + double chartSampleRate = settings.MinimumChartSamplesPerCycle; + TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); + XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); + XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); + templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.MinimumChartSamplesPerCycle); + templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); + + SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); + LoadSentEmail(email, xdaNow, recipients, htmlDocument, eventIDs); + } + finally + { + attachments?.ForEach(attachment => attachment.Dispose()); + } + } + + private void LoadSentEmail(EmailType email, DateTime now, List recipients, XDocument htmlDocument, List eventIDs) + { + int sentEmailID = LoadSentEmail(email, now, recipients, htmlDocument); + + if (eventIDs.Count == 0) + return; + + using (AdoDataConnection connection = ConnectionFactory()) + { + TableOperations eventSentEmailTable = new TableOperations(connection); + + foreach (int eventID in eventIDs) + { + if (eventSentEmailTable.QueryRecordCountWhere("EventID = {0} AND SentEmailID = {1}", eventID, sentEmailID) > 0) + continue; + + EventSentEmail eventSentEmail = new EventSentEmail(); + eventSentEmail.EventID = eventID; + eventSentEmail.SentEmailID = sentEmailID; + eventSentEmailTable.AddNewRecord(eventSentEmail); + } + } + } + + private List GetAssetGroups(List eventIDs) + { + if (eventIDs.Count == 0) + return new List(); + + string query = + $"SELECT DISTINCT AssetGroup.* " + + $"FROM " + + $" AssetGroup LEFT OUTER JOIN " + + $" MeterAssetGroup ON MeterAssetGroup.AssetGroupID = AssetGroup.ID LEFT OUTER JOIN " + + $" AssetAssetGroup ON AssetAssetGroup.AssetGroupID = AssetGroup.ID JOIN " + + $" Event ON " + + $" Event.ID IN ({string.Join(",", eventIDs)}) AND " + + $" (" + + $" Event.MeterID = MeterAssetGroup.MeterID OR " + + $" Event.AssetID = AssetAssetGroup.AssetID " + + $" )"; + + using (AdoDataConnection connection = ConnectionFactory()) + using (DataTable table = connection.RetrieveData(query)) + { + TableOperations assetGroupTable = new TableOperations(connection); + + return table + .AsEnumerable() + .Select(assetGroupTable.LoadRecord) + .ToList(); + } + } + } +} diff --git a/Source/Libraries/FaultData/FaultData.csproj b/Source/Libraries/FaultData/FaultData.csproj index 94831303a5..ae91624c79 100644 --- a/Source/Libraries/FaultData/FaultData.csproj +++ b/Source/Libraries/FaultData/FaultData.csproj @@ -184,6 +184,7 @@ + diff --git a/Source/Libraries/openXDA.Nodes/Types/Email/EventEmailNode.cs b/Source/Libraries/openXDA.Nodes/Types/Email/EventEmailNode.cs index f6c9be5810..b6528b1ad8 100644 --- a/Source/Libraries/openXDA.Nodes/Types/Email/EventEmailNode.cs +++ b/Source/Libraries/openXDA.Nodes/Types/Email/EventEmailNode.cs @@ -254,7 +254,7 @@ private void SendEmail(EmailType parameters, List events) if (!eventEmailSettings.Enabled || Tripped) return; - EmailService emailService = new EmailService(CreateDbConnection, configurator); + TriggeredEmailService emailService = new TriggeredEmailService(CreateDbConnection, configurator); DateTime now = DateTime.UtcNow; TimeZoneConverter timeZoneConverter = new TimeZoneConverter(configurator); @@ -301,7 +301,7 @@ private void Restore() Tripped = false; } - private void SendTripNotification(EventEmailSection eventEmailSettings, EmailService emailService) + private void SendTripNotification(EventEmailSection eventEmailSettings, TriggeredEmailService emailService) { string subject = "openXDA email flooding detected"; StringBuilder message = new StringBuilder(); From 32fd23f0489eb4d370c10e111e93689f39e637ef Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Thu, 16 May 2024 10:26:39 -0400 Subject: [PATCH 10/12] Extract scheduled email functions to their own subclass --- .../Controllers/WebAPI/EmailController.cs | 4 +- .../DataWriters/Emails/EmailService.cs | 99 ------------ .../Emails/ScheduledEmailService.cs | 142 ++++++++++++++++++ Source/Libraries/FaultData/FaultData.csproj | 1 + .../Types/Email/ScheduledEmailNode.cs | 4 +- 5 files changed, 147 insertions(+), 103 deletions(-) create mode 100644 Source/Libraries/FaultData/DataWriters/Emails/ScheduledEmailService.cs diff --git a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs index 9c8720b734..f409efec57 100644 --- a/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs +++ b/Source/Applications/openXDA/openXDA/Controllers/WebAPI/EmailController.cs @@ -209,7 +209,7 @@ public IHttpActionResult SendTestReport(int reportID, string userID, string curr { Action configurator = GetConfigurator(); Settings settings = new Settings(configurator); - EmailService emailService = new EmailService(CreateDbConnection, configurator); + ScheduledEmailService emailService = new ScheduledEmailService(CreateDbConnection, configurator); using (AdoDataConnection connection = CreateDbConnection()) { @@ -228,7 +228,7 @@ public IHttpActionResult SendTestReport(int reportID, string userID, string curr if (!DateTime.TryParse(current, out DateTime xdaNow)) xdaNow = DateTime.UtcNow; - emailService.SendScheduledEmail(report, new List() { account.Email }, out EmailResponse emailResponse, xdaNow); + emailService.SendEmail(report, new List() { account.Email }, out EmailResponse emailResponse, xdaNow); response.DataSourceResponses = emailResponse.DataSources; return Ok(response); } diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 63692c387c..7203a4a2e7 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -118,24 +118,6 @@ public EmailService(Func connectionFactory, Action co #region [ Methods ] - public bool SendScheduledEmail(ScheduledEmailType email, DateTime xdaNow) - { - List recipients = GetRecipients(email); - - if (recipients.Count == 0 && string.IsNullOrEmpty(email.FilePath)) - return false; - - SendScheduledEmail(email, recipients, true, xdaNow); - - return true; - } - - public void SendScheduledEmail(ScheduledEmailType email, List recipients, out EmailResponse response, DateTime xdaNow) => - SendScheduledEmail(email, recipients, false, out response, xdaNow); - - public void SendScheduledEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => - SendScheduledEmail(email, recipients, saveToFile, out EmailResponse _, xdaNow); - public void SendEmail(List recipients, XDocument htmlDocument, List attachments, EmailTypeBase emailType, EmailResponse email, string filePath = null) => SendEmail(recipients, htmlDocument, attachments, emailType, QuerySettings(), email, filePath); @@ -197,37 +179,6 @@ public void SendEmail(List recipients, XDocument htmlDocument, List recipients, bool saveToFile, out EmailResponse response, DateTime xdaNow) - { - List attachments = new List(); - - response = new EmailResponse(); - - try - { - EmailSection settings = QuerySettings(); - - ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(ConnectionFactory); - List definitions = factory.LoadDataSourceDefinitions(email); - IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, xdaNow)); - response.DataSources.AddRange(dataSourceResponses); - - double chartSampleRate = settings.MinimumChartSamplesPerCycle; - TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); - XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); - XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); - templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.MinimumChartSamplesPerCycle); - templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); - - SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); - LoadSentEmail(email, xdaNow, recipients, htmlDocument); - } - finally - { - attachments?.ForEach(attachment => attachment.Dispose()); - } - } - public void SendAdminEmail(string subject, string message, List replyToRecipients) { Settings settings = new Settings(Configure); @@ -314,56 +265,6 @@ public List GetRecipients(EmailType emailType) } } - public List GetRecipients(ScheduledEmailType emailType) - { - string emailAddressQuery; - Func processor; - - if (!emailType.SMS) - { - bool requireEmailConfirm; - using (AdoDataConnection connection = ConnectionFactory()) - requireEmailConfirm = connection.ExecuteScalar("SELECT Value From [Setting] Where Name = 'Subscription.RequireConfirmation'"); - - emailAddressQuery = - "SELECT DISTINCT UserAccount.Email AS Email " + - "FROM " + - " UserAccountScheduledEmailType JOIN " + - " UserAccount ON UserAccountScheduledEmailType.UserAccountID = UserAccount.ID " + - "WHERE " + - " UserAccountScheduledEmailType.ScheduledEmailTypeID = {0} AND " + - (requireEmailConfirm ? " UserAccount.EmailConfirmed <> 0 AND " : "") + - " UserAccount.Approved <> 0"; - - processor = row => row.ConvertField("Email"); - } - else - { - emailAddressQuery = - "SELECT DISTINCT UserAccount.Phone AS Phone, CellCarrier.Transform as Transform " + - "FROM " + - " UserAccountScheduledEmailType JOIN " + - " UserAccount ON UserAccountScheduledEmailType.UserAccountID = UserAccount.ID LEFT JOIN" + - " UserAccountCarrier ON UserAccountCarrier.UserAccountID = UserAccount.ID LEFT JOIN " + - " CellCarrier ON UserAccountCarrier.CarrierID = CellCarrier.ID " + - "WHERE " + - " UserAccountScheduledEmailType.ScheduledEmailTypeID = {0} AND " + - " UserAccount.PhoneConfirmed <> 0 AND " + - " UserAccount.Approved <> 0"; - - processor = row => string.Format(row.ConvertField("Transform"), row.ConvertField("Phone")); - } - - using (AdoDataConnection connection = ConnectionFactory()) - using (DataTable emailAddressTable = connection.RetrieveData(emailAddressQuery, emailType.ID)) - { - return emailAddressTable - .Select() - .Select(processor) - .ToList(); - } - } - protected int LoadSentEmail(EmailTypeBase email, DateTime now, List recipients, XDocument htmlDocument) { using (AdoDataConnection connection = ConnectionFactory()) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/ScheduledEmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledEmailService.cs new file mode 100644 index 0000000000..d4e941c5c0 --- /dev/null +++ b/Source/Libraries/FaultData/DataWriters/Emails/ScheduledEmailService.cs @@ -0,0 +1,142 @@ +//****************************************************************************************************** +// ScheduledEmailService.cs - Gbtc +// +// Copyright © 2024, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 05/16/2024 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net.Mail; +using System.Xml.Linq; +using GSF.Data; +using openXDA.Configuration; +using openXDA.Model; + +namespace FaultData.DataWriters.Emails +{ + public class ScheduledEmailService : EmailService + { + public ScheduledEmailService(Func connectionFactory, Action configure) + : base(connectionFactory, configure) + { + } + + public bool SendEmail(ScheduledEmailType email, DateTime xdaNow) + { + List recipients = GetRecipients(email); + + if (recipients.Count == 0 && string.IsNullOrEmpty(email.FilePath)) + return false; + + SendEmail(email, recipients, true, xdaNow); + + return true; + } + + public void SendEmail(ScheduledEmailType email, List recipients, out EmailResponse response, DateTime xdaNow) => + SendEmail(email, recipients, false, out response, xdaNow); + + public void SendEmail(ScheduledEmailType email, List recipients, bool saveToFile, DateTime xdaNow) => + SendEmail(email, recipients, saveToFile, out EmailResponse _, xdaNow); + + private void SendEmail(ScheduledEmailType email, List recipients, bool saveToFile, out EmailResponse response, DateTime xdaNow) + { + List attachments = new List(); + + response = new EmailResponse(); + + try + { + EmailSection settings = QuerySettings(); + + ScheduledDataSourceFactory factory = new ScheduledDataSourceFactory(ConnectionFactory); + List definitions = factory.LoadDataSourceDefinitions(email); + IEnumerable dataSourceResponses = definitions.Select(definition => definition.CreateAndProcess(factory, xdaNow)); + response.DataSources.AddRange(dataSourceResponses); + + double chartSampleRate = settings.MinimumChartSamplesPerCycle; + TemplateProcessor templateProcessor = new TemplateProcessor(ConnectionFactory); + XElement templateData = new XElement("data", response.DataSources.Select(r => r.Data)); + XDocument htmlDocument = templateProcessor.ApplyTemplate(email, templateData.ToString()); + templateProcessor.ApplyChartTransform(attachments, htmlDocument, settings.MinimumChartSamplesPerCycle); + templateProcessor.ApplyImageEmbedTransform(attachments, htmlDocument); + + SendEmail(recipients, htmlDocument, attachments, email, settings, response, (saveToFile ? email.FilePath : null)); + LoadSentEmail(email, xdaNow, recipients, htmlDocument); + } + finally + { + attachments?.ForEach(attachment => attachment.Dispose()); + } + } + + public List GetRecipients(ScheduledEmailType emailType) + { + string emailAddressQuery; + Func processor; + + if (!emailType.SMS) + { + bool requireEmailConfirm; + using (AdoDataConnection connection = ConnectionFactory()) + requireEmailConfirm = connection.ExecuteScalar("SELECT Value From [Setting] Where Name = 'Subscription.RequireConfirmation'"); + + emailAddressQuery = + "SELECT DISTINCT UserAccount.Email AS Email " + + "FROM " + + " UserAccountScheduledEmailType JOIN " + + " UserAccount ON UserAccountScheduledEmailType.UserAccountID = UserAccount.ID " + + "WHERE " + + " UserAccountScheduledEmailType.ScheduledEmailTypeID = {0} AND " + + (requireEmailConfirm ? " UserAccount.EmailConfirmed <> 0 AND " : "") + + " UserAccount.Approved <> 0"; + + processor = row => row.ConvertField("Email"); + } + else + { + emailAddressQuery = + "SELECT DISTINCT UserAccount.Phone AS Phone, CellCarrier.Transform as Transform " + + "FROM " + + " UserAccountScheduledEmailType JOIN " + + " UserAccount ON UserAccountScheduledEmailType.UserAccountID = UserAccount.ID LEFT JOIN" + + " UserAccountCarrier ON UserAccountCarrier.UserAccountID = UserAccount.ID LEFT JOIN " + + " CellCarrier ON UserAccountCarrier.CarrierID = CellCarrier.ID " + + "WHERE " + + " UserAccountScheduledEmailType.ScheduledEmailTypeID = {0} AND " + + " UserAccount.PhoneConfirmed <> 0 AND " + + " UserAccount.Approved <> 0"; + + processor = row => string.Format(row.ConvertField("Transform"), row.ConvertField("Phone")); + } + + using (AdoDataConnection connection = ConnectionFactory()) + using (DataTable emailAddressTable = connection.RetrieveData(emailAddressQuery, emailType.ID)) + { + return emailAddressTable + .Select() + .Select(processor) + .ToList(); + } + } + } +} diff --git a/Source/Libraries/FaultData/FaultData.csproj b/Source/Libraries/FaultData/FaultData.csproj index ae91624c79..6f04c78d5b 100644 --- a/Source/Libraries/FaultData/FaultData.csproj +++ b/Source/Libraries/FaultData/FaultData.csproj @@ -178,6 +178,7 @@ + diff --git a/Source/Libraries/openXDA.Nodes/Types/Email/ScheduledEmailNode.cs b/Source/Libraries/openXDA.Nodes/Types/Email/ScheduledEmailNode.cs index cc65531919..137ab2ec23 100644 --- a/Source/Libraries/openXDA.Nodes/Types/Email/ScheduledEmailNode.cs +++ b/Source/Libraries/openXDA.Nodes/Types/Email/ScheduledEmailNode.cs @@ -176,9 +176,9 @@ private void ProcessScheduledEmail(int scheduledEmailID, DateTime now) if (!triggersEmail) return; - EmailService emailService = new EmailService(CreateDbConnection, configurator); + ScheduledEmailService emailService = new ScheduledEmailService(CreateDbConnection, configurator); - emailService.SendScheduledEmail(scheduledEmailType, xdaNow); + emailService.SendEmail(scheduledEmailType, xdaNow); } } From 94d2f56bd799c74ad84d01c25979e1fdf5950712 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Thu, 16 May 2024 10:29:45 -0400 Subject: [PATCH 11/12] Remove unused DataSourceWrapper class from EmailService --- .../DataWriters/Emails/EmailService.cs | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 7203a4a2e7..9a98d9396a 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -55,41 +55,6 @@ public Settings(Action configure) => public EmailSection EmailSettings { get; } = new EmailSection(); } - private class DataSourceWrapper - { - public string Name { get; } - public ITriggeredDataSource DataSourceTriggered { get; } - public IScheduledDataSource DataSourceScheduled { get; } - - public DataSourceWrapper(string name, ITriggeredDataSource dataSourceTriggered = null, IScheduledDataSource dataSourceScheduled = null) - { - Name = name; - DataSourceTriggered = dataSourceTriggered; - DataSourceScheduled = dataSourceScheduled; - } - public XElement TryProcess(DateTime xdaNow) => TryProcess(xdaNow, out _); - - public XElement TryProcess(DateTime xdaNow, out Exception exception) - { - if (DataSourceScheduled is null) - { - exception = new NullReferenceException("DataSource was not created."); - Log.Debug($"Email data source {Name} was not created", exception); - return null; - } - - XElement element = null; - exception = null; - try { element = DataSourceScheduled.Process(xdaNow); } - catch (Exception ex) { exception = ex; } - - if (!(exception is null)) - Log.Error($"Email data source {Name} failed to process", exception); - - return element; - } - } - public class EmailResponse { public List DataSources { get; } = new List(); From 7fe3889174dfa9cd2849837def599f9c904b331a Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Thu, 16 May 2024 12:56:12 -0400 Subject: [PATCH 12/12] Remove unnecessary escape character replacements --- Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs index 9a98d9396a..afd7c34b9a 100644 --- a/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs +++ b/Source/Libraries/FaultData/DataWriters/Emails/EmailService.cs @@ -285,9 +285,7 @@ private string GetSubject(XDocument htmlDocument, EmailTypeBase emailType) private string GetBody(XDocument htmlDocument) => htmlDocument .ToString(SaveOptions.DisableFormatting) - .Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">"); + .Replace("&", "&"); #endregion