From 07317f928f71c88486c73e3e2183d2eb6bcde476 Mon Sep 17 00:00:00 2001 From: Ariana D Mihailescu <82962995+arianamihailescu@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:49:39 -0400 Subject: [PATCH 1/5] LNK-2738: multi-measure reporting changes --- DotNet/Tenant/Config/TenantConstants.cs | 3 +- .../Tenant/Controllers/FacilityController.cs | 33 +-- DotNet/Tenant/Entities/FacilityConfigModel.cs | 12 +- .../Entities/MonthlyReportingPlanModel.cs | 13 - .../Tenant/Entities/ScheduledReportModel.cs | 11 + DotNet/Tenant/Entities/ScheduledTaskModel.cs | 16 -- .../IFacilityConfigurationService.cs | 17 ++ DotNet/Tenant/Jobs/ReportScheduledJob.cs | 73 ++++-- ...0911180606_MultiMeasureChanges.Designer.cs | 88 +++++++ .../20240911180606_MultiMeasureChanges.cs | 70 ++++++ .../FacilityDbContextModelSnapshot.cs | 85 +------ DotNet/Tenant/Models/FacilityConfigDto.cs | 10 +- .../Models/Messages/ReportScheduledKey.cs | 3 +- .../Models/Messages/ReportScheduledMessage.cs | 6 +- .../Tenant/Models/MonthlyReportingPlanDto.cs | 12 - DotNet/Tenant/Models/ScheduledReportDto.cs | 14 ++ DotNet/Tenant/Models/ScheduledTaskDto.cs | 32 --- DotNet/Tenant/Program.cs | 2 +- .../Repository/Mappings/FacilityConfigMap.cs | 32 +-- .../Services/FacilityConfigurationService.cs | 193 +++++---------- DotNet/Tenant/Services/ScheduleService.cs | 224 +++++++++--------- DotNet/Tenant/facilitiesMultiMeasure.sql | 131 ++++++++++ .../CreateFacilityConfigurationTests.cs | 187 +++++++++------ .../GetFacilityConfigurationTests.cs | 22 +- .../RemoveFacilityConfigurationTests.cs | 7 +- .../UpdateFacilityConfigurationTests.cs | 162 ++++++------- 26 files changed, 810 insertions(+), 648 deletions(-) delete mode 100644 DotNet/Tenant/Entities/MonthlyReportingPlanModel.cs create mode 100644 DotNet/Tenant/Entities/ScheduledReportModel.cs delete mode 100644 DotNet/Tenant/Entities/ScheduledTaskModel.cs create mode 100644 DotNet/Tenant/Interfaces/IFacilityConfigurationService.cs create mode 100644 DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.Designer.cs create mode 100644 DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.cs delete mode 100644 DotNet/Tenant/Models/MonthlyReportingPlanDto.cs create mode 100644 DotNet/Tenant/Models/ScheduledReportDto.cs delete mode 100644 DotNet/Tenant/Models/ScheduledTaskDto.cs create mode 100644 DotNet/Tenant/facilitiesMultiMeasure.sql diff --git a/DotNet/Tenant/Config/TenantConstants.cs b/DotNet/Tenant/Config/TenantConstants.cs index c622cc124..94c4eca3d 100644 --- a/DotNet/Tenant/Config/TenantConstants.cs +++ b/DotNet/Tenant/Config/TenantConstants.cs @@ -28,11 +28,12 @@ public static class Scheduler public const string Facility = "Facility"; - public const string ReportType = "ReportType"; + public const string Frequency = "Frequency"; public const string StartDate = "StartDate"; public const string EndDate = "EndDate"; + } } diff --git a/DotNet/Tenant/Controllers/FacilityController.cs b/DotNet/Tenant/Controllers/FacilityController.cs index d2c1d300d..4f35fc61d 100644 --- a/DotNet/Tenant/Controllers/FacilityController.cs +++ b/DotNet/Tenant/Controllers/FacilityController.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using LantanaGroup.Link.Shared.Application.Enums; using LantanaGroup.Link.Shared.Application.Models.Responses; +using LantanaGroup.Link.Tenant.Interfaces; namespace LantanaGroup.Link.Tenant.Controllers { @@ -18,7 +19,7 @@ namespace LantanaGroup.Link.Tenant.Controllers public class FacilityController : ControllerBase { - private readonly FacilityConfigurationService _facilityConfigurationService; + private readonly IFacilityConfigurationService _facilityConfigurationService; private readonly IMapper _mapperModelToDto; @@ -30,7 +31,7 @@ public class FacilityController : ControllerBase public IScheduler _scheduler { get; set; } - public FacilityController(ILogger logger, FacilityConfigurationService facilityConfigurationService, ISchedulerFactory schedulerFactory) + public FacilityController(ILogger logger, IFacilityConfigurationService facilityConfigurationService, ISchedulerFactory schedulerFactory) { _facilityConfigurationService = facilityConfigurationService; @@ -42,18 +43,14 @@ public FacilityController(ILogger logger, FacilityConfigurat { cfg.CreateMap(); cfg.CreateMap, PagedFacilityConfigDto>(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); + cfg.CreateMap(); }); var configDtoToModel = new MapperConfiguration(cfg => { cfg.CreateMap(); cfg.CreateMap>(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); + cfg.CreateMap(); }); _mapperModelToDto = configModelToDto.CreateMapper(); @@ -199,8 +196,6 @@ public async Task UpdateFacility(string id, FacilityConfigDto upd FacilityConfigModel dest = _mapperDtoToModel.Map(updatedFacility); - FacilityConfigModel existingFacility = await _facilityConfigurationService.GetFacilityById(id, cancellationToken); - // validate id and updatedFacility.id match if (id.ToString() != updatedFacility.Id) { @@ -208,9 +203,14 @@ public async Task UpdateFacility(string id, FacilityConfigDto upd return BadRequest($" {id} in the url and the {updatedFacility.Id} in the payload mismatch"); } + + FacilityConfigModel oldFacility = await _facilityConfigurationService.GetFacilityById(id, cancellationToken); + + FacilityConfigModel clonedFacility = oldFacility.ShallowCopy(); + + try { - await _facilityConfigurationService.UpdateFacility(id, dest, cancellationToken); } catch (ApplicationException ex) @@ -226,12 +226,13 @@ public async Task UpdateFacility(string id, FacilityConfigDto upd throw; } - // if existingFacility is not null, then update the jobs, else add new jobs - if (existingFacility != null) + // if clonedFacility is not null, then update the jobs, else add new jobs + + if (clonedFacility != null) { using (ServiceActivitySource.Instance.StartActivity("Update Jobs for Facility")) { - await ScheduleService.UpdateJobsForFacility(dest, existingFacility, _scheduler); + await ScheduleService.UpdateJobsForFacility(dest, clonedFacility, _scheduler); } } else @@ -242,7 +243,7 @@ public async Task UpdateFacility(string id, FacilityConfigDto upd } } - if (existingFacility == null) + if (oldFacility == null) { return CreatedAtAction(nameof(StoreFacility), new { id = dest.Id }, dest); } @@ -279,7 +280,7 @@ public async Task DeleteFacility(string facilityId, CancellationT using (ServiceActivitySource.Instance.StartActivity("Delete Jobs for Facility")) { - await ScheduleService.DeleteJobsForFacility(existingFacility.Id.ToString(), existingFacility.ScheduledTasks, _scheduler); + await ScheduleService.DeleteJobsForFacility(existingFacility.Id.ToString(), _scheduler); } return NoContent(); diff --git a/DotNet/Tenant/Entities/FacilityConfigModel.cs b/DotNet/Tenant/Entities/FacilityConfigModel.cs index 152bb4948..95c6c1f6e 100644 --- a/DotNet/Tenant/Entities/FacilityConfigModel.cs +++ b/DotNet/Tenant/Entities/FacilityConfigModel.cs @@ -1,17 +1,17 @@ using LantanaGroup.Link.Shared.Domain.Entities; -using System.ComponentModel.DataAnnotations.Schema; namespace LantanaGroup.Link.Tenant.Entities { - [Table("Facilities")] + public class FacilityConfigModel : BaseEntityExtended { public string FacilityId { get; set; } = null!; public string? FacilityName { get; set; } - public List? ScheduledTasks { get; set; } = new List(); - public List? MonthlyReportingPlans { get; set; } = new List(); - public DateTime? MRPModifyDate { get; set; } - public DateTime? MRPCreatedDate { get; set; } + public ScheduledReportModel ScheduledReports { get; set; } = null!; + public FacilityConfigModel ShallowCopy() + { + return (FacilityConfigModel)this.MemberwiseClone(); + } } } diff --git a/DotNet/Tenant/Entities/MonthlyReportingPlanModel.cs b/DotNet/Tenant/Entities/MonthlyReportingPlanModel.cs deleted file mode 100644 index 5dbeea6bf..000000000 --- a/DotNet/Tenant/Entities/MonthlyReportingPlanModel.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace LantanaGroup.Link.Tenant.Entities -{ - public class MonthlyReportingPlanModel - { - public string? ReportType { get; set; } - - public int? ReportMonth { get; set; } - - public int? ReportYear { get; set; } - - } -} diff --git a/DotNet/Tenant/Entities/ScheduledReportModel.cs b/DotNet/Tenant/Entities/ScheduledReportModel.cs new file mode 100644 index 000000000..2305a2864 --- /dev/null +++ b/DotNet/Tenant/Entities/ScheduledReportModel.cs @@ -0,0 +1,11 @@ +namespace LantanaGroup.Link.Tenant.Entities +{ + public class ScheduledReportModel + { + public string[] Daily { get; set; } + + public string[] Weekly { get; set; } + + public string[] Monthly { get; set; } + } +} diff --git a/DotNet/Tenant/Entities/ScheduledTaskModel.cs b/DotNet/Tenant/Entities/ScheduledTaskModel.cs deleted file mode 100644 index 1c731241b..000000000 --- a/DotNet/Tenant/Entities/ScheduledTaskModel.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace LantanaGroup.Link.Tenant.Entities -{ - public class ScheduledTaskModel - { - public string? KafkaTopic { get; set; } = string.Empty; - public List ReportTypeSchedules { get; set; } = new List(); - - public class ReportTypeSchedule - { - public string? ReportType { get; set; } - public List? ScheduledTriggers { get; set; } = new List(); - } - } - -} diff --git a/DotNet/Tenant/Interfaces/IFacilityConfigurationService.cs b/DotNet/Tenant/Interfaces/IFacilityConfigurationService.cs new file mode 100644 index 000000000..b5b6847a9 --- /dev/null +++ b/DotNet/Tenant/Interfaces/IFacilityConfigurationService.cs @@ -0,0 +1,17 @@ +using LantanaGroup.Link.Shared.Application.Enums; +using LantanaGroup.Link.Shared.Application.Models.Responses; +using LantanaGroup.Link.Tenant.Entities; + +namespace LantanaGroup.Link.Tenant.Interfaces +{ + public interface IFacilityConfigurationService + { + Task CreateFacility(FacilityConfigModel newFacility, CancellationToken cancellationToken); + Task> GetAllFacilities(CancellationToken cancellationToken = default); + Task> GetFacilities(string? facilityId, string? facilityName, string? sortBy, SortOrder? sortOrder, int pageSize = 10, int pageNumber = 1, CancellationToken cancellationToken = default); + Task GetFacilityByFacilityId(string facilityId, CancellationToken cancellationToken); + Task GetFacilityById(string id, CancellationToken cancellationToken); + Task RemoveFacility(string facilityId, CancellationToken cancellationToken); + Task UpdateFacility(string id, FacilityConfigModel newFacility, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/DotNet/Tenant/Jobs/ReportScheduledJob.cs b/DotNet/Tenant/Jobs/ReportScheduledJob.cs index 9a9959cef..9b5538ca9 100644 --- a/DotNet/Tenant/Jobs/ReportScheduledJob.cs +++ b/DotNet/Tenant/Jobs/ReportScheduledJob.cs @@ -6,6 +6,8 @@ using LantanaGroup.Link.Tenant.Entities; using LantanaGroup.Link.Tenant.Interfaces; using LantanaGroup.Link.Tenant.Models.Messages; +using LantanaGroup.Link.Tenant.Services; +using MongoDB.Driver.Linq; using Quartz; using System.Text.Json; @@ -16,15 +18,14 @@ namespace LantanaGroup.Link.Tenant.Jobs public class ReportScheduledJob : IJob { private readonly ILogger _logger; - //private readonly IKafkaProducerFactory _kafkaProducerFactory; - private readonly IProducer _producer; + private readonly IKafkaProducerFactory _kafkaProducerFactory; private readonly ITenantServiceMetrics _metrics; - public ReportScheduledJob(ILogger logger, ITenantServiceMetrics metrics, IProducer producer) + public ReportScheduledJob(ILogger logger, IKafkaProducerFactory kafkaProducerFactory, ITenantServiceMetrics metrics) { _logger = logger; + _kafkaProducerFactory = kafkaProducerFactory ?? throw new ArgumentNullException(nameof(kafkaProducerFactory)); _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); - _producer = producer ?? throw new ArgumentNullException(nameof(producer)); } public async Task Execute(IJobExecutionContext context) @@ -35,30 +36,48 @@ public async Task Execute(IJobExecutionContext context) JobDataMap triggerMap = context.Trigger.JobDataMap!; + String[] reportTypes = []; + string trigger = (string)triggerMap[TenantConstants.Scheduler.JobTrigger]; FacilityConfigModel facility = (FacilityConfigModel)dataMap[TenantConstants.Scheduler.Facility]; - string reportType = (string)dataMap[TenantConstants.Scheduler.ReportType]; - - List> parameters = new List>(); - - /* DateTime startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1, 0, 0, 0); - - DateTime endDate1 = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0); - - DateTime endDate = endDate1.AddMinutes(2); - */ + string frequency = (string)dataMap[TenantConstants.Scheduler.Frequency]; - DateTime startDate = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1); - DateTime endDate = startDate.AddMonths(1).AddSeconds(-1); + DateTime currentDateInTimeZone = DateTime.UtcNow; - parameters.Add(new KeyValuePair(TenantConstants.Scheduler.StartDate, startDate)); + // initialize startDate, endDate + DateTime startDate = currentDateInTimeZone; + DateTime endDate = currentDateInTimeZone; - parameters.Add(new KeyValuePair(TenantConstants.Scheduler.EndDate, endDate)); - - _logger.LogInformation($"Produce {KafkaTopic.ReportScheduled} + event for facility {facility.FacilityId} and {reportType} and trigger: {trigger}"); + // adjust startDate, endDate based on frequency + switch (frequency) + { + case ScheduleService.MONTHLY: + startDate = new DateTime(currentDateInTimeZone.Year, currentDateInTimeZone.Month, 1); + endDate = startDate.AddMonths(1).AddSeconds(-1); + reportTypes = facility.ScheduledReports.Monthly; + break; + case ScheduleService.WEEKLY: + startDate = new DateTime(currentDateInTimeZone.Year, currentDateInTimeZone.Month, currentDateInTimeZone.Day); + // set to beginning of week in case is not exactly that + DayOfWeek startOfWeek = DayOfWeek.Sunday; + DayOfWeek currentDay = startDate.DayOfWeek; + int difference = currentDay - startOfWeek; + startDate = startDate.AddDays(-difference); + // end date of the week + endDate = startDate.AddDays(7).AddSeconds(-1); + reportTypes = facility.ScheduledReports.Weekly; + break; + case ScheduleService.DAILY: + startDate = new DateTime(currentDateInTimeZone.Year, currentDateInTimeZone.Month, currentDateInTimeZone.Day); + endDate = startDate.AddDays(1).AddSeconds(-1); + reportTypes = facility.ScheduledReports.Daily; + break; + } + + _logger.LogInformation($"Produce {KafkaTopic.ReportScheduled} + event for facility {facility.FacilityId} and {frequency} trigger: {trigger}"); var headers = new Headers(); string correlationId = Guid.NewGuid().ToString(); @@ -68,7 +87,6 @@ public async Task Execute(IJobExecutionContext context) ReportScheduledKey Key = new ReportScheduledKey() { FacilityId = facility.FacilityId, - ReportType = reportType }; var message = new Message @@ -77,15 +95,22 @@ public async Task Execute(IJobExecutionContext context) Headers = headers, Value = new ReportScheduledMessage() { - Parameters = parameters + ReportTypes = reportTypes, + Frequency = frequency, + StartDate = startDate, + EndDate = endDate }, }; - await _producer.ProduceAsync(KafkaTopic.ReportScheduled.ToString(), message); + var producerConfig = new ProducerConfig(); + + var producer = _kafkaProducerFactory.CreateProducer(producerConfig); + + await producer.ProduceAsync(KafkaTopic.ReportScheduled.ToString(), message); _metrics.IncrementReportScheduledCounter([ new KeyValuePair(DiagnosticNames.FacilityId, facility.FacilityId), - new KeyValuePair(DiagnosticNames.ReportType, reportType), + new KeyValuePair(DiagnosticNames.ReportType, reportTypes), new KeyValuePair(DiagnosticNames.PeriodStart, startDate), new KeyValuePair(DiagnosticNames.PeriodEnd, endDate) ]); diff --git a/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.Designer.cs b/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.Designer.cs new file mode 100644 index 000000000..65c031134 --- /dev/null +++ b/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.Designer.cs @@ -0,0 +1,88 @@ +// +using System; +using LantanaGroup.Link.Tenant.Repository.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LantanaGroup.Link.Tenant.Migrations +{ + [DbContext(typeof(FacilityDbContext))] + [Migration("20240911180606_MultiMeasureChanges")] + partial class MultiMeasureChanges + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("LantanaGroup.Link.Tenant.Entities.FacilityConfigModel", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("FacilityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FacilityName") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifyDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); + + b.ToTable("Facilities", (string)null); + }); + + modelBuilder.Entity("LantanaGroup.Link.Tenant.Entities.FacilityConfigModel", b => + { + b.OwnsOne("LantanaGroup.Link.Tenant.Entities.ScheduledReportModel", "ScheduledReports", b1 => + { + b1.Property("FacilityConfigModelId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Daily") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Monthly") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Weekly") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("FacilityConfigModelId"); + + b1.ToTable("Facilities"); + + b1.ToJson("ScheduledReports"); + + b1.WithOwner() + .HasForeignKey("FacilityConfigModelId"); + }); + + b.Navigation("ScheduledReports") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.cs b/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.cs new file mode 100644 index 000000000..ba08ec455 --- /dev/null +++ b/DotNet/Tenant/Migrations/20240911180606_MultiMeasureChanges.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LantanaGroup.Link.Tenant.Migrations +{ + /// + public partial class MultiMeasureChanges : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MRPCreatedDate", + table: "Facilities"); + + migrationBuilder.DropColumn( + name: "MRPModifyDate", + table: "Facilities"); + + migrationBuilder.DropColumn( + name: "MonthlyReportingPlans", + table: "Facilities"); + + migrationBuilder.DropColumn( + name: "ScheduledTasks", + table: "Facilities"); + + migrationBuilder.AddColumn( + name: "ScheduledReports", + table: "Facilities", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ScheduledReports", + table: "Facilities"); + + migrationBuilder.AddColumn( + name: "MRPCreatedDate", + table: "Facilities", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "MRPModifyDate", + table: "Facilities", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "MonthlyReportingPlans", + table: "Facilities", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "ScheduledTasks", + table: "Facilities", + type: "nvarchar(max)", + nullable: true); + } + } +} diff --git a/DotNet/Tenant/Migrations/FacilityDbContextModelSnapshot.cs b/DotNet/Tenant/Migrations/FacilityDbContextModelSnapshot.cs index e0ba70bc7..31b70a8f5 100644 --- a/DotNet/Tenant/Migrations/FacilityDbContextModelSnapshot.cs +++ b/DotNet/Tenant/Migrations/FacilityDbContextModelSnapshot.cs @@ -25,7 +25,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("LantanaGroup.Link.Tenant.Entities.FacilityConfigModel", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("CreateDate") @@ -41,12 +40,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifyDate") .HasColumnType("datetime2"); - b.Property("MRPCreatedDate") - .HasColumnType("datetime2"); - - b.Property("MRPModifyDate") - .HasColumnType("datetime2"); - b.HasKey("Id"); SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); @@ -56,89 +49,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("LantanaGroup.Link.Tenant.Entities.FacilityConfigModel", b => { - b.OwnsMany("LantanaGroup.Link.Tenant.Entities.MonthlyReportingPlanModel", "MonthlyReportingPlans", b1 => + b.OwnsOne("LantanaGroup.Link.Tenant.Entities.ScheduledReportModel", "ScheduledReports", b1 => { b1.Property("FacilityConfigModelId") .HasColumnType("uniqueidentifier"); - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b1.Property("ReportMonth") - .HasColumnType("int"); - - b1.Property("ReportType") + b1.Property("Daily") + .IsRequired() .HasColumnType("nvarchar(max)"); - b1.Property("ReportYear") - .HasColumnType("int"); - - b1.HasKey("FacilityConfigModelId", "Id"); - - b1.ToTable("Facilities"); - - b1.ToJson("MonthlyReportingPlans"); - - b1.WithOwner() - .HasForeignKey("FacilityConfigModelId"); - }); - - b.OwnsMany("LantanaGroup.Link.Tenant.Entities.ScheduledTaskModel", "ScheduledTasks", b1 => - { - b1.Property("FacilityConfigModelId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); + b1.Property("Monthly") + .IsRequired() + .HasColumnType("nvarchar(max)"); - b1.Property("KafkaTopic") + b1.Property("Weekly") + .IsRequired() .HasColumnType("nvarchar(max)"); - b1.HasKey("FacilityConfigModelId", "Id"); + b1.HasKey("FacilityConfigModelId"); b1.ToTable("Facilities"); - b1.ToJson("ScheduledTasks"); + b1.ToJson("ScheduledReports"); b1.WithOwner() .HasForeignKey("FacilityConfigModelId"); - - b1.OwnsMany("LantanaGroup.Link.Tenant.Entities.ScheduledTaskModel+ReportTypeSchedule", "ReportTypeSchedules", b2 => - { - b2.Property("ScheduledTaskModelFacilityConfigModelId") - .HasColumnType("uniqueidentifier"); - - b2.Property("ScheduledTaskModelId") - .HasColumnType("int"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b2.Property("ReportType") - .HasColumnType("nvarchar(max)"); - - b2.Property("ScheduledTriggers") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("ScheduledTaskModelFacilityConfigModelId", "ScheduledTaskModelId", "Id"); - - b2.ToTable("Facilities"); - - b2.ToJson("ReportTypeSchedules"); - - b2.WithOwner() - .HasForeignKey("ScheduledTaskModelFacilityConfigModelId", "ScheduledTaskModelId"); - }); - - b1.Navigation("ReportTypeSchedules"); }); - b.Navigation("MonthlyReportingPlans"); - - b.Navigation("ScheduledTasks"); + b.Navigation("ScheduledReports") + .IsRequired(); }); #pragma warning restore 612, 618 } diff --git a/DotNet/Tenant/Models/FacilityConfigDto.cs b/DotNet/Tenant/Models/FacilityConfigDto.cs index f11346c91..db2a16440 100644 --- a/DotNet/Tenant/Models/FacilityConfigDto.cs +++ b/DotNet/Tenant/Models/FacilityConfigDto.cs @@ -1,15 +1,11 @@ -using LantanaGroup.Link.Tenant.Entities; - -namespace LantanaGroup.Link.Tenant.Models +namespace LantanaGroup.Link.Tenant.Models { public class FacilityConfigDto { public string? Id { get; set; } public string FacilityId { get; set; } = null!; public string? FacilityName { get; set; } - public List? ScheduledTasks { get; set; } = new List(); - public List? MonthlyReportingPlans { get; set; } = new List(); - public DateTime CreateDate { get; set; } - public DateTime? ModifyDate { get; set; } + public ScheduledReportDto ScheduledReports { get; set; } = null!; + } } diff --git a/DotNet/Tenant/Models/Messages/ReportScheduledKey.cs b/DotNet/Tenant/Models/Messages/ReportScheduledKey.cs index f23c0ad9a..6d34aeb6c 100644 --- a/DotNet/Tenant/Models/Messages/ReportScheduledKey.cs +++ b/DotNet/Tenant/Models/Messages/ReportScheduledKey.cs @@ -3,6 +3,5 @@ public class ReportScheduledKey { public string? FacilityId { get; set; } - public string? ReportType { get; set; } } -} \ No newline at end of file +} diff --git a/DotNet/Tenant/Models/Messages/ReportScheduledMessage.cs b/DotNet/Tenant/Models/Messages/ReportScheduledMessage.cs index a9c71637d..e9ccae8dd 100644 --- a/DotNet/Tenant/Models/Messages/ReportScheduledMessage.cs +++ b/DotNet/Tenant/Models/Messages/ReportScheduledMessage.cs @@ -2,5 +2,9 @@ namespace LantanaGroup.Link.Tenant.Models.Messages; public class ReportScheduledMessage { - public List> Parameters { get; set; } = new List>(); + public string[] ReportTypes { get; set; } + public string Frequency { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } diff --git a/DotNet/Tenant/Models/MonthlyReportingPlanDto.cs b/DotNet/Tenant/Models/MonthlyReportingPlanDto.cs deleted file mode 100644 index c2ac85b76..000000000 --- a/DotNet/Tenant/Models/MonthlyReportingPlanDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace LantanaGroup.Link.Tenant.Models -{ - public class MonthlyReportingPlanDto - { - public string? ReportType { get; set; } - - public int? ReportMonth { get; set; } - - public int? ReportYear { get; set; } - - } -} diff --git a/DotNet/Tenant/Models/ScheduledReportDto.cs b/DotNet/Tenant/Models/ScheduledReportDto.cs new file mode 100644 index 000000000..e69c3af58 --- /dev/null +++ b/DotNet/Tenant/Models/ScheduledReportDto.cs @@ -0,0 +1,14 @@ +using CronExpressionDescriptor; + +namespace LantanaGroup.Link.Tenant.Models +{ + public class ScheduledReportDto + { + public string[] Daily { get; set; } + + public string[] Weekly { get; set; } + + public string[] Monthly { get; set; } + } + +} diff --git a/DotNet/Tenant/Models/ScheduledTaskDto.cs b/DotNet/Tenant/Models/ScheduledTaskDto.cs deleted file mode 100644 index 2dd45c1b2..000000000 --- a/DotNet/Tenant/Models/ScheduledTaskDto.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CronExpressionDescriptor; - -namespace LantanaGroup.Link.Tenant.Models -{ - public class ScheduledTaskDto - { - public string? KafkaTopic { get; set; } = string.Empty; - - public List ReportTypeSchedules { get; set; } = new List(); - - public class ReportTypeDtoSchedule - { - public string? ReportType { get; set; } - public List ScheduledTriggers { get; set; } = new List(); - - public List? ScheduledTriggerDescription - { - get - { - var scheduledTriggersList = new List(); - foreach (string scheduledTrigger in ScheduledTriggers) - { - scheduledTriggersList.Add(ExpressionDescriptor.GetDescription(scheduledTrigger)); - } - return scheduledTriggersList; - } - - } - } - } - -} diff --git a/DotNet/Tenant/Program.cs b/DotNet/Tenant/Program.cs index b05170569..243349896 100644 --- a/DotNet/Tenant/Program.cs +++ b/DotNet/Tenant/Program.cs @@ -103,7 +103,7 @@ static void RegisterServices(WebApplicationBuilder builder) builder.Services.Configure(builder.Configuration.GetSection(ConfigurationConstants.AppSettings.CORS)); builder.Services.Configure(builder.Configuration.GetSection(ConfigurationConstants.AppSettings.LinkTokenService)); - builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/DotNet/Tenant/Repository/Mappings/FacilityConfigMap.cs b/DotNet/Tenant/Repository/Mappings/FacilityConfigMap.cs index f1f59c8d0..9d6b46026 100644 --- a/DotNet/Tenant/Repository/Mappings/FacilityConfigMap.cs +++ b/DotNet/Tenant/Repository/Mappings/FacilityConfigMap.cs @@ -13,27 +13,29 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(b => b.Id).IsClustered(false); - var comp = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer>( - (c1, c2) => c1.SequenceEqual(c2), - c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), - c => c.ToList()); + /* var comp = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer>( + (c1, c2) => c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToList());*/ - builder.OwnsMany(facilityConfig => facilityConfig.ScheduledTasks, navBuilder => + builder.OwnsOne(facilityConfig => facilityConfig.ScheduledReports, navBuilder => { navBuilder.ToJson(); - navBuilder.OwnsMany(st => st.ReportTypeSchedules, navBuilder => - { - navBuilder.ToJson(); - navBuilder.Property(st => st.ScheduledTriggers).HasConversion(v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject>(v)).Metadata.SetValueComparer(comp); - }); - }); - builder.OwnsMany(facilityConfig => facilityConfig.MonthlyReportingPlans, navBuilder => - { - navBuilder.ToJson(); - }); + /* navBuilder.OwnsMany(st => st.ReportTypeSchedules, navBuilder => + { + navBuilder.ToJson(); + navBuilder.Property(st => st.ScheduledTriggers).HasConversion(v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject>(v)).Metadata.SetValueComparer(comp); + });*/ + + + + /* builder.OwnsMany(facilityConfig => facilityConfig.MonthlyReportingPlans, navBuilder => + { + navBuilder.ToJson(); + });*/ } } } diff --git a/DotNet/Tenant/Services/FacilityConfigurationService.cs b/DotNet/Tenant/Services/FacilityConfigurationService.cs index e1aaa980e..7afc9f6bb 100644 --- a/DotNet/Tenant/Services/FacilityConfigurationService.cs +++ b/DotNet/Tenant/Services/FacilityConfigurationService.cs @@ -14,20 +14,17 @@ using System.Diagnostics; using System.Net.Http.Headers; using System.Text; -using static LantanaGroup.Link.Tenant.Entities.ScheduledTaskModel; using LantanaGroup.Link.Shared.Application.Interfaces.Services.Security.Token; using LantanaGroup.Link.Shared.Application.Models.Responses; -using System.Linq.Expressions; -using Confluent.Kafka; +using LantanaGroup.Link.Tenant.Interfaces; namespace LantanaGroup.Link.Tenant.Services { - public class FacilityConfigurationService - { + public class FacilityConfigurationService : IFacilityConfigurationService { - private readonly ILogger _logger; - private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private HttpClient _httpClient; private static List _topics = new List(); private readonly IOptions _serviceRegistry; private readonly IFacilityConfigurationRepo _facilityConfigurationRepo; @@ -42,8 +39,7 @@ static FacilityConfigurationService() _topics.Add(KafkaTopic.ReportScheduled); } - - public FacilityConfigurationService(IFacilityConfigurationRepo facilityConfigurationRepo, ILogger logger, CreateAuditEventCommand createAuditEventCommand, IOptions serviceRegistry, IOptions measureConfig, HttpClient httpClient, IOptions linkTokenServiceConfig, ICreateSystemToken createSystemToken) + public FacilityConfigurationService(IFacilityConfigurationRepo facilityConfigurationRepo, ILogger logger, CreateAuditEventCommand createAuditEventCommand, IOptions serviceRegistry, IOptions measureConfig, HttpClient httpClient, IOptions linkTokenServiceConfig, ICreateSystemToken createSystemToken) { _facilityConfigurationRepo = facilityConfigurationRepo; _serviceRegistry = serviceRegistry ?? throw new ArgumentNullException(nameof(serviceRegistry)); @@ -76,10 +72,18 @@ public async Task> GetFacilities(string? f } else { + if (sortBy == null) + { + sortBy = "FacilityId"; + } + if (sortOrder == null) + { + sortOrder = SortOrder.Ascending; + } (List facilities, PaginationMetadata metadata) = await _facilityConfigurationRepo.SearchAsync(null, sortBy, sortOrder, pageSize, pageNumber, cancellationToken); pagedNotificationConfigurations = new PagedConfigModel(facilities, metadata); } - + return pagedNotificationConfigurations; } @@ -111,7 +115,7 @@ public async Task CreateFacility(FacilityConfigModel newFacility, CancellationTo var facility = await GetFacilityByFacilityId(newFacility.FacilityId, cancellationToken); - // validates facility does not exist + // validates facility if (facility is not null) { _logger.LogError($"Facility {newFacility.FacilityId} already exists"); @@ -119,7 +123,8 @@ public async Task CreateFacility(FacilityConfigModel newFacility, CancellationTo throw new ApplicationException($"Facility {newFacility.FacilityId} already exists"); } - await ValidateSchedules(newFacility); + + await this.ValidateSchedules(newFacility); } try @@ -127,8 +132,6 @@ public async Task CreateFacility(FacilityConfigModel newFacility, CancellationTo using (ServiceActivitySource.Instance.StartActivity("Create the Facility Configuration Command")) { - newFacility.MRPCreatedDate = DateTime.UtcNow; - //newFacility.Id = Guid.NewGuid(); await _facilityConfigurationRepo.AddAsync(newFacility, cancellationToken); } } @@ -178,7 +181,7 @@ public async Task UpdateFacility(String id, FacilityConfigModel newFacil await ValidateSchedules(newFacility); } - + // audit update facility event AuditEventMessage auditMessageEvent = Helper.UpdateFacilityAuditEvent(newFacility, existingFacility); @@ -190,18 +193,13 @@ public async Task UpdateFacility(String id, FacilityConfigModel newFacil { existingFacility.FacilityId = newFacility.FacilityId; existingFacility.FacilityName = newFacility.FacilityName; - existingFacility.ScheduledTasks = newFacility.ScheduledTasks; - existingFacility.MonthlyReportingPlans = newFacility.MonthlyReportingPlans; - + existingFacility.ScheduledReports = newFacility.ScheduledReports; await _facilityConfigurationRepo.UpdateAsync(existingFacility, cancellationToken); } else { - newFacility.MRPCreatedDate = DateTime.UtcNow; - newFacility.Id = id; await _facilityConfigurationRepo.AddAsync(newFacility, cancellationToken); } - } } catch (Exception ex) @@ -284,152 +282,77 @@ private void ValidateFacility(FacilityConfigModel facility) { throw new ApplicationException(validationErrors.ToString()); } - // validate monthly reporting plans fields - if (facility.MonthlyReportingPlans != null) - { - foreach (MonthlyReportingPlanModel monthlyReportingPlan in facility.MonthlyReportingPlans) - { - if (string.IsNullOrWhiteSpace(monthlyReportingPlan.ReportType)) - { - validationErrors.AppendLine("ReportType for MonthlyReportingPlans must be entered."); - } - if (monthlyReportingPlan.ReportMonth == null) - { - validationErrors.AppendLine("ReportMonth for MonthlyReportingPlans must be entered."); - } - if (monthlyReportingPlan.ReportYear == null) - { - validationErrors.AppendLine("ReportYear for MonthlyReportingPlans must be entered."); - } - } - } } private async Task ValidateSchedules(FacilityConfigModel facility) { - if (facility.ScheduledTasks == null) return; + List reportTypes = new List(); + reportTypes.AddRange(facility.ScheduledReports.Monthly); + reportTypes.AddRange(facility.ScheduledReports.Daily); + reportTypes.AddRange(facility.ScheduledReports.Weekly); - foreach (ScheduledTaskModel scheduledTask in facility.ScheduledTasks) - { - // validate topic - if (scheduledTask.KafkaTopic != null && !Helper.ValidateTopic(scheduledTask.KafkaTopic, _topics)) - { - throw new ApplicationException($"kafka topic {scheduledTask.KafkaTopic} is Invalid. Valid values are {string.Join(" and ", _topics.ToList())}"); - } - - // validate scheduleTrigger - foreach (ScheduledTaskModel.ReportTypeSchedule reportTypeSchedule in scheduledTask.ReportTypeSchedules) - { - if (reportTypeSchedule.ScheduledTriggers != null) - { - foreach (string trigger in reportTypeSchedule.ScheduledTriggers) - { - if (!Helper.IsValidSchedule(trigger)) - { - _logger.LogError($"ScheduledTrigger {trigger} for facility {facility.FacilityId} and kafka topic {scheduledTask.KafkaTopic} is not valid chron expression"); - throw new ApplicationException($"ScheduledTrigger {trigger} for facility {facility.FacilityId} and kafka topic {scheduledTask.KafkaTopic} is not valid chron expression"); - } - } - } - } - } - // validate topic is unique within Facility - - IEnumerable duplicates = facility.ScheduledTasks.GroupBy(i => i.KafkaTopic).Where(g => g.Count() > 1).Select(g => g.Key); - - string duplicatedTopics = string.Join(" ", duplicates.ToList()); - - if (!duplicatedTopics.Equals("")) - { - _logger.LogError($"The following topics {duplicatedTopics} are duplicated for facility {facility.FacilityId} "); - throw new ApplicationException($"The following topics {duplicatedTopics} are duplicated for facility {facility.FacilityId} "); - } - // validate report Type within Topic - StringBuilder schedulingErrors = new StringBuilder(); - foreach (ScheduledTaskModel facilityScheduledTask in facility.ScheduledTasks) + HashSet duplicates = FindDuplicates(reportTypes); + if (duplicates.Count > 0) { - - IEnumerable duplicatedReportTypes = facilityScheduledTask.ReportTypeSchedules.GroupBy(i => i.ReportType).Where(g => g.Count() > 1).Select(g => g.Key); - - string duplicatedReportTypesString = string.Join(" ", duplicatedReportTypes.ToList()); - - if (!duplicatedReportTypesString.Equals("")) - { - _logger.LogError($"The following ReportTypes {duplicatedReportTypesString} are duplicated for facility {facility.FacilityId} and KafakaTopic {facilityScheduledTask.KafkaTopic}"); - throw new ApplicationException($"The following ReportTypes {duplicatedReportTypesString} are duplicated for facility {facility.FacilityId} and KafakaTopic {facilityScheduledTask.KafkaTopic}"); - } - - int i = 1; - foreach (ReportTypeSchedule reportTypeSchedule in facilityScheduledTask.ReportTypeSchedules) - { - // validate reportType not null and report trigers not emty - if (string.IsNullOrWhiteSpace(reportTypeSchedule.ReportType)) - { - schedulingErrors.AppendLine($"Report Type under KafkaTopic {facilityScheduledTask.KafkaTopic} and ReportTypeSchedule Number {i} must be specified."); - } - - // validate the reportype is one of the known values - if (!MeasureDefinitionTypeValidation.Validate(reportTypeSchedule.ReportType)) - { - throw new ApplicationException($"ReportType {reportTypeSchedule.ReportType} is not a known report type."); - } - - // check if the report type was set-up in Measure Evaluation Service - await checkIfMeasureEvalExists(reportTypeSchedule); - - if (reportTypeSchedule.ScheduledTriggers.Count == 0) - { - schedulingErrors.AppendLine($"Scheduled triggers under KafkaTopic {facilityScheduledTask.KafkaTopic} and ReportTypeSchedule Number {i} must be specified."); - } - - i++; - - IEnumerable duplicatedTriggers = reportTypeSchedule.ScheduledTriggers.GroupBy(i => i).Where(g => g.Count() > 1).Select(g => g.Key); - - string duplicatedTriggersString = string.Join(" ", duplicatedTriggers.ToList()); - - if (!duplicatedTriggersString.Equals("")) - { - _logger.LogError($"The following Trigger {duplicatedTriggersString} are duplicated for facility {facility.FacilityId} and KafakaTopic {facilityScheduledTask.KafkaTopic} and reportType {reportTypeSchedule.ReportType}"); - throw new ApplicationException($"The following Trigger {duplicatedTriggersString} are duplicated for facility {facility.FacilityId} and KafakaTopic {facilityScheduledTask.KafkaTopic} and reportType {reportTypeSchedule.ReportType}"); - } - - } + _logger.LogError("Duplicate entries found: " + string.Join(", ", duplicates)); + throw new ApplicationException("Duplicate entries found: " + string.Join(", ", duplicates)); } - if (!string.IsNullOrEmpty(schedulingErrors.ToString())) + // validate report types exist + foreach (var reportType in reportTypes) { - throw new ApplicationException(schedulingErrors.ToString()); + await MeasureDefinitionExists(reportType); } + return; } - private async Task checkIfMeasureEvalExists(ReportTypeSchedule reportTypeSchedule) + private async Task MeasureDefinitionExists(String reportType) { if (_measureConfig.Value.CheckIfMeasureExists) { if (String.IsNullOrEmpty(_serviceRegistry.Value.MeasureServiceUrl)) throw new ApplicationException($"MeasureEval service configuration from \"ServiceRegistry.MeasureServiceUrl\" is missing"); - string requestUrl = _serviceRegistry.Value.MeasureServiceUrl + $"/api/measure-definition/{reportTypeSchedule.ReportType}"; + string requestUrl = _serviceRegistry.Value.MeasureServiceUrl + $"/api/measure-definition/{reportType}"; - //TODO: add method to get key that includes looking at redis for future use case if (_linkTokenServiceConfig.Value.SigningKey is null) throw new Exception("Link Token Service Signing Key is missing."); //get link token - var token = await _createSystemToken.ExecuteAsync(_linkTokenServiceConfig.Value.SigningKey, 2); + if (_linkTokenServiceConfig.Value.EnableTokenGenerationEndpoint) + { + var token = await _createSystemToken.ExecuteAsync(_linkTokenServiceConfig.Value.SigningKey, 2); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - var response = _httpClient.GetAsync(requestUrl).Result; + var response = await _httpClient.GetAsync(requestUrl, CancellationToken.None); // check respone status code if (!response.IsSuccessStatusCode) { - throw new ApplicationException($"Report Type {reportTypeSchedule.ReportType} is not setup in MeasureEval service."); + throw new ApplicationException($"Report Type {reportType} is not setup in MeasureEval service."); } + } + return; } + + static HashSet FindDuplicates(List list) + { + HashSet uniqueItems = new HashSet(); + HashSet duplicates = new HashSet(); + + foreach (string item in list) + { + if (!uniqueItems.Add(item)) + { + duplicates.Add(item); + } + } + return duplicates; + } + } } diff --git a/DotNet/Tenant/Services/ScheduleService.cs b/DotNet/Tenant/Services/ScheduleService.cs index 353973788..779c3cb70 100644 --- a/DotNet/Tenant/Services/ScheduleService.cs +++ b/DotNet/Tenant/Services/ScheduleService.cs @@ -14,35 +14,25 @@ namespace LantanaGroup.Link.Tenant.Services public class ScheduleService : IHostedService { - private readonly ISchedulerFactory _schedulerFactory; - private readonly Quartz.Spi.IJobFactory _jobFactory; - - private static Dictionary _topicJobs = new Dictionary(); + public const string MONTHLY = "monthly"; + public const string WEEKLY = "weekly"; + public const string DAILY = "daily"; + private readonly ISchedulerFactory _schedulerFactory; + private readonly IJobFactory _jobFactory; private readonly IServiceScopeFactory _scopeFactory; - private readonly FacilityConfigurationService _facilityConfigurationService; - - static ScheduleService() - { - _topicJobs.Add(KafkaTopic.ReportScheduled.ToString(), typeof(ReportScheduledJob)); - _topicJobs.Add(KafkaTopic.RetentionCheckScheduled.ToString(), typeof(RetentionCheckScheduledJob)); - } - public ScheduleService( ISchedulerFactory schedulerFactory, IServiceScopeFactory serviceScopeFactory, - // FacilityConfigurationService facilityConfigurationService, IJobFactory jobFactory) { _schedulerFactory = schedulerFactory; _jobFactory = jobFactory; - // _facilityConfigurationService = facilityConfigurationService; _scopeFactory = serviceScopeFactory; } public IScheduler Scheduler { get; set; } - // static ConcurrentDictionary jobs = new ConcurrentDictionary(); public async Task StartAsync(CancellationToken cancellationToken) { @@ -50,15 +40,13 @@ public async Task StartAsync(CancellationToken cancellationToken) Scheduler.JobFactory = _jobFactory; - //List facilities = _facilityConfigurationService.GetFacilities(); - using (var scope = _scopeFactory.CreateScope()) { - FacilityConfigurationService facilityConfigurationService = scope.ServiceProvider.GetRequiredService(); - var facilities = await facilityConfigurationService.GetAllFacilities(cancellationToken); + var _context = scope.ServiceProvider.GetRequiredService(); + var facilities = await _context.Facilities.ToListAsync(); foreach (FacilityConfigModel facility in facilities) { - await AddJobsForFacility(facility, Scheduler); + await AddJobsForFacility(facility, Scheduler); } } @@ -71,178 +59,182 @@ public async Task StopAsync(CancellationToken cancellationToken) await Scheduler?.Shutdown(cancellationToken); } - public static async Task AddJobsForFacility(FacilityConfigModel facility, IScheduler scheduler) + public static Task AddJobsForFacility(FacilityConfigModel facility, IScheduler scheduler) { - foreach (ScheduledTaskModel task in facility.ScheduledTasks) + // create a job and trigger for monthly reports + if (facility.ScheduledReports.Monthly.Length > 0) { + createJobAndTrigger(facility, MONTHLY, scheduler); + } - foreach (ScheduledTaskModel.ReportTypeSchedule reportTypeSchedule in task.ReportTypeSchedules) - { - createJobAndTrigger(facility, task.KafkaTopic.ToString(), reportTypeSchedule, scheduler); - } + //create a job and trigger for weekly reports + if (facility.ScheduledReports.Weekly.Length > 0) + { + createJobAndTrigger(facility, WEEKLY, scheduler); + } + // create a job and trigger for daily reports + if (facility.ScheduledReports.Daily.Length > 0) + { + createJobAndTrigger(facility, DAILY, scheduler); } + + return Task.CompletedTask; } - public static async Task DeleteJobsForFacility(String facilityId, List? jobsToBeDeleted, IScheduler scheduler) + public static async Task DeleteJobsForFacility(String facilityId, IScheduler scheduler, List? frequencies = null) { - if (jobsToBeDeleted != null) + frequencies ??= [ MONTHLY, WEEKLY, DAILY ]; + + if (frequencies != null) { - foreach (ScheduledTaskModel task in jobsToBeDeleted) + foreach (String frequency in frequencies) { - if (task.KafkaTopic == null) continue; + var groupMatcher = GroupMatcher.GroupContains(nameof(KafkaTopic.ReportScheduled)); - var groupMatcher = GroupMatcher.GroupContains(task.KafkaTopic); + string jobKeyName = $"{facilityId}-{frequency}"; - foreach (ScheduledTaskModel.ReportTypeSchedule reportTypeSchedule in task.ReportTypeSchedules) + JobKey jobKey = new JobKey(jobKeyName) { - string jobKeyName = $"{facilityId}-{reportTypeSchedule.ReportType}"; - - JobKey jobKey = scheduler.GetJobKeys(groupMatcher).Result.FirstOrDefault(key => key.Name == jobKeyName); + Group = nameof(KafkaTopic.ReportScheduled) + }; - IReadOnlyCollection triggers = scheduler.GetTriggersOfJob(jobKey).Result; - - foreach (ITrigger trigger in triggers) - { - TriggerKey oldTrigger = trigger.Key; - - await scheduler.UnscheduleJob(oldTrigger); - } + IJobDetail? job = await scheduler.GetJobDetail(jobKey); + if (job != null) + { + await scheduler.DeleteJob(job.Key); } } } } - public static async Task UpdateJobsForFacility(FacilityConfigModel newFacility, FacilityConfigModel existingFacility, IScheduler scheduler) + public static async Task DeleteJob(string facilityId, IScheduler scheduler) { - // delete jobs that are in existing facility but not in the new one - - List tasksToBeDeleted = existingFacility.ScheduledTasks.Where(existingScheduledTask => !newFacility.ScheduledTasks.Select(newScheduledTask => newScheduledTask.KafkaTopic).Contains(existingScheduledTask.KafkaTopic)).ToList(); - - foreach (ScheduledTaskModel existingScheduledTask in existingFacility.ScheduledTasks) + JobKey jobKey = new JobKey(facilityId) { - ScheduledTaskModel taskModel = new ScheduledTaskModel(); + Group = nameof(KafkaTopic.ReportScheduled) + }; - var newTask = newFacility.ScheduledTasks.FirstOrDefault(newScheduledTask => newScheduledTask.KafkaTopic == existingScheduledTask.KafkaTopic); + IJobDetail job = await scheduler.GetJobDetail(jobKey); - if (newTask is not null) - { - List schedulesToBeDeleted = existingScheduledTask.ReportTypeSchedules.Where(existingReportTypeScheduled => !newTask.ReportTypeSchedules.Select(newReportTypeScheduled => newReportTypeScheduled.ReportType).Contains(existingReportTypeScheduled.ReportType)).ToList(); + if (job != null) + { + await scheduler.DeleteJob(job.Key); + } + } - taskModel.KafkaTopic = existingScheduledTask.KafkaTopic; - taskModel.ReportTypeSchedules.AddRange(schedulesToBeDeleted ?? schedulesToBeDeleted); - tasksToBeDeleted.Add(taskModel); - } + public static async Task UpdateJobsForFacility(FacilityConfigModel updatedFacility, FacilityConfigModel existingFacility, IScheduler scheduler) + { + + List frequencies = []; + if (!updatedFacility.ScheduledReports.Monthly.Distinct().OrderBy(x => x).SequenceEqual(existingFacility.ScheduledReports.Monthly.Distinct().OrderBy(x => x))) + { + frequencies.Add(MONTHLY); + } + if (!updatedFacility.ScheduledReports.Weekly.Distinct().OrderBy(x => x).SequenceEqual(existingFacility.ScheduledReports.Weekly.Distinct().OrderBy(x => x))) + { + frequencies.Add(WEEKLY); } - - ScheduleService.DeleteJobsForFacility(newFacility.Id.ToString(), tasksToBeDeleted, scheduler); - - // update and add new jobs - - foreach (ScheduledTaskModel task in newFacility.ScheduledTasks) + if (!updatedFacility.ScheduledReports.Daily.Distinct().OrderBy(x => x).SequenceEqual(existingFacility.ScheduledReports.Daily.Distinct().OrderBy(x => x))) { - var groupMatcher = GroupMatcher.GroupContains(task.KafkaTopic.ToString()); - - foreach (ScheduledTaskModel.ReportTypeSchedule reportTypeSchedule in task.ReportTypeSchedules) - { - string jobKeyName = $"{newFacility.Id}-{reportTypeSchedule.ReportType}"; - - JobKey jobKey = scheduler.GetJobKeys(groupMatcher).Result.FirstOrDefault(key => key.Name == jobKeyName); - - if (jobKey is not null) - { - await RescheduleJob(reportTypeSchedule, jobKey, scheduler); - } - else - { - createJobAndTrigger(newFacility, task.KafkaTopic, reportTypeSchedule, scheduler); - - } - } + frequencies.Add(DAILY); } - } - - public static async Task RescheduleJob(ScheduledTaskModel.ReportTypeSchedule task, JobKey jobKey, IScheduler scheduler) - { - IReadOnlyCollection triggers = scheduler.GetTriggersOfJob(jobKey).Result; + // delete jobs that are in existing facility but not in the updated one - foreach (ITrigger trigger in triggers) + if (frequencies.Count() > 0) { - TriggerKey oldTrigger = trigger.Key; + await DeleteJobsForFacility(updatedFacility.Id.ToString(), scheduler, frequencies); + } - await scheduler.UnscheduleJob(oldTrigger); + // recreate jobs for updated facility for all frequencies + if (frequencies.Contains(MONTHLY) && updatedFacility.ScheduledReports.Monthly.Length > 0) + { + createJobAndTrigger(updatedFacility, MONTHLY, scheduler); } - foreach (string trigger in task.ScheduledTriggers) + + if (frequencies.Contains(WEEKLY) && updatedFacility.ScheduledReports.Weekly.Length > 0) { - ITrigger newTrigger = CreateTrigger(trigger, jobKey); + createJobAndTrigger(updatedFacility, WEEKLY, scheduler); + } - await scheduler.ScheduleJob(newTrigger); + if (frequencies.Contains(DAILY) && updatedFacility.ScheduledReports.Daily.Length > 0) + { + createJobAndTrigger(updatedFacility, DAILY, scheduler); } } - - public static async void createJobAndTrigger(FacilityConfigModel facility, string topic, ScheduledTaskModel.ReportTypeSchedule reportTypeSchedule, IScheduler scheduler) + public static async void createJobAndTrigger(FacilityConfigModel facility, string frequency, IScheduler scheduler) { - _topicJobs.TryGetValue(topic, out Type jobType); - if (jobType is null) throw new ApplicationException($"Job Type not found for {topic}"); - if (reportTypeSchedule.ReportType is null) throw new ApplicationException($"Report Type not found for {topic}"); - - IJobDetail job = CreateJob(jobType, facility, reportTypeSchedule.ReportType, topic); + IJobDetail job = CreateJob(facility, frequency); await scheduler.AddJob(job, true); - if (reportTypeSchedule.ScheduledTriggers != null) - { - foreach (string scheduledTrigger in reportTypeSchedule.ScheduledTriggers) - { - ITrigger trigger = CreateTrigger(scheduledTrigger, job.Key); - await scheduler.ScheduleJob(trigger); - } - } + ITrigger trigger = CreateTrigger(frequency, job.Key); + + await scheduler.ScheduleJob(trigger); + } - public static IJobDetail CreateJob(Type jobType, FacilityConfigModel facility, string reportType, string topic) + public static IJobDetail CreateJob(FacilityConfigModel facility, string frequency) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.Put(TenantConstants.Scheduler.Facility, facility); - jobDataMap.Put(TenantConstants.Scheduler.ReportType, reportType); + jobDataMap.Put(TenantConstants.Scheduler.Frequency, frequency); - string jobName = $"{facility.Id}-{reportType}"; + string jobName = $"{facility.Id}-{frequency}"; return JobBuilder - .Create(jobType) + .Create(typeof(ReportScheduledJob)) .StoreDurably() - .WithIdentity(jobName, topic) - .WithDescription($"{jobName}-{topic}") + .WithIdentity(jobName, nameof(KafkaTopic.ReportScheduled)) + .WithDescription($"{jobName}") .UsingJobData(jobDataMap) .Build(); } - public static ITrigger CreateTrigger(string ScheduledTrigger, JobKey jobKey) + public static ITrigger CreateTrigger(string frequency, JobKey jobKey) { JobDataMap jobDataMap = new JobDataMap(); + string ScheduledTrigger = ""; + + // determine the chron trigger based on frequency + switch (frequency) + { + case MONTHLY: + ScheduledTrigger = "0 0 0 1 * ? *"; + break; + case WEEKLY: + ScheduledTrigger = "0 0 0 ? * 1 *"; + break; + case DAILY: + ScheduledTrigger = "0 0 0 * * ? *"; + break; + } + jobDataMap.Put(TenantConstants.Scheduler.JobTrigger, ScheduledTrigger); + return TriggerBuilder .Create() .ForJob(jobKey) .WithIdentity(Guid.NewGuid().ToString(), jobKey.Group) - .WithCronSchedule(ScheduledTrigger) + .WithCronSchedule(ScheduledTrigger, x => x.InTimeZone(TimeZoneInfo.Utc)) .WithDescription(ScheduledTrigger) .UsingJobData(jobDataMap) .Build(); } + public static void GetAllJobs(IScheduler scheduler) { var jobGroups = scheduler.GetJobGroupNames().Result; diff --git a/DotNet/Tenant/facilitiesMultiMeasure.sql b/DotNet/Tenant/facilitiesMultiMeasure.sql new file mode 100644 index 000000000..651e0a1fa --- /dev/null +++ b/DotNet/Tenant/facilitiesMultiMeasure.sql @@ -0,0 +1,131 @@ +IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; +GO + +BEGIN TRANSACTION; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240723171206_AddFacilitiesTable' +) +BEGIN + CREATE TABLE [Facilities] ( + [Id] uniqueidentifier NOT NULL, + [FacilityId] nvarchar(max) NOT NULL, + [FacilityName] nvarchar(max) NULL, + [MRPModifyDate] datetime2 NULL, + [MRPCreatedDate] datetime2 NULL, + [CreateDate] datetime2 NOT NULL, + [ModifyDate] datetime2 NULL, + [MonthlyReportingPlans] nvarchar(max) NULL, + [ScheduledTasks] nvarchar(max) NULL, + CONSTRAINT [PK_Facilities] PRIMARY KEY NONCLUSTERED ([Id]) + ); +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240723171206_AddFacilitiesTable' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'20240723171206_AddFacilitiesTable', N'8.0.3'); +END; +GO + +COMMIT; +GO + +BEGIN TRANSACTION; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + DECLARE @var0 sysname; + SELECT @var0 = [d].[name] + FROM [sys].[default_constraints] [d] + INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] + WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Facilities]') AND [c].[name] = N'MRPCreatedDate'); + IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Facilities] DROP CONSTRAINT [' + @var0 + '];'); + ALTER TABLE [Facilities] DROP COLUMN [MRPCreatedDate]; +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + DECLARE @var1 sysname; + SELECT @var1 = [d].[name] + FROM [sys].[default_constraints] [d] + INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] + WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Facilities]') AND [c].[name] = N'MRPModifyDate'); + IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Facilities] DROP CONSTRAINT [' + @var1 + '];'); + ALTER TABLE [Facilities] DROP COLUMN [MRPModifyDate]; +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + DECLARE @var2 sysname; + SELECT @var2 = [d].[name] + FROM [sys].[default_constraints] [d] + INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] + WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Facilities]') AND [c].[name] = N'MonthlyReportingPlans'); + IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Facilities] DROP CONSTRAINT [' + @var2 + '];'); + ALTER TABLE [Facilities] DROP COLUMN [MonthlyReportingPlans]; +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + DECLARE @var3 sysname; + SELECT @var3 = [d].[name] + FROM [sys].[default_constraints] [d] + INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] + WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Facilities]') AND [c].[name] = N'ScheduledTasks'); + IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [Facilities] DROP CONSTRAINT [' + @var3 + '];'); + ALTER TABLE [Facilities] DROP COLUMN [ScheduledTasks]; +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + ALTER TABLE [Facilities] ADD [ScheduledReports] nvarchar(max) NOT NULL DEFAULT N''; +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'20240911180606_MultiMeasureChanges' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'20240911180606_MultiMeasureChanges', N'8.0.3'); +END; +GO + +COMMIT; +GO + diff --git a/DotNet/TenantTests/CreateFacilityConfigurationTests.cs b/DotNet/TenantTests/CreateFacilityConfigurationTests.cs index b233a2190..9fa4980cd 100644 --- a/DotNet/TenantTests/CreateFacilityConfigurationTests.cs +++ b/DotNet/TenantTests/CreateFacilityConfigurationTests.cs @@ -1,10 +1,11 @@ using Confluent.Kafka; -using LantanaGroup.Link.Shared.Application.Interfaces; +using LantanaGroup.Link.Shared.Application.Interfaces.Services.Security.Token; using LantanaGroup.Link.Shared.Application.Models; using LantanaGroup.Link.Shared.Application.Models.Configs; -using LantanaGroup.Link.Shared.Application.Models.Kafka; +using LantanaGroup.Link.Tenant.Commands; using LantanaGroup.Link.Tenant.Entities; +using LantanaGroup.Link.Tenant.Interfaces; using LantanaGroup.Link.Tenant.Models; using LantanaGroup.Link.Tenant.Repository.Interfaces.Sql; using LantanaGroup.Link.Tenant.Services; @@ -12,7 +13,9 @@ using Microsoft.Extensions.Options; using Moq; using Moq.AutoMock; -using static LantanaGroup.Link.Tenant.Entities.ScheduledTaskModel; +using Moq.Protected; +using System.Linq.Expressions; +using System.Net; namespace TenantTests { @@ -20,115 +23,163 @@ public class CreateFacilityConfigurationTests { private FacilityConfigModel? _model; private ServiceRegistry? _serviceRegistry; + private LinkTokenServiceSettings? _linkTokenService; + private MeasureConfig? _linkMeasureConfig; private const string facilityId = "TestFacility_002"; private const string facilityName = "TestFacility_002"; - private static readonly List scheduledTaskModels = new List(); + private List facilities = new List(); - private AutoMocker?_mocker; - private FacilityConfigurationService? _service; + private AutoMocker? _mocker; + private IFacilityConfigurationService? _service; public ILogger logger = Mock.Of>(); - [Fact] - public async Task TestCreateFacility() + private void SetUp() { - scheduledTaskModels.Add(new ScheduledTaskModel() { KafkaTopic = KafkaTopic.ReportScheduled.ToString(), ReportTypeSchedules = new List() }); _model = new FacilityConfigModel() { FacilityId = facilityId, FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now, + ScheduledReports = new ScheduledReportModel(), CreateDate = DateTime.Now, ModifyDate = DateTime.Now }; + _model.ScheduledReports.Daily = new string[] { "NHSNdQMAcuteCareHospitalInitialPopulation" }; + _model.ScheduledReports.Weekly = new string[] { "NHSNRespiratoryPathogenSurveillanceInitialPopulation" }; + _model.ScheduledReports.Monthly = new string[] { "NHSNGlycemicControlHypoglycemicInitialPopulation" }; + _serviceRegistry = new ServiceRegistry() { - MeasureServiceUrl = "test" + MeasureServiceUrl = "http://localhost:5678" }; - _model.ScheduledTasks.AddRange(scheduledTaskModels); - - _mocker = new AutoMocker(); + _linkTokenService = new LinkTokenServiceSettings() + { + SigningKey = "test" + }; - _service = _mocker.CreateInstance(); + _linkMeasureConfig = new MeasureConfig() + { + CheckIfMeasureExists = true, + }; - _ = _mocker.GetMock() - .Setup(p => p.AddAsync(_model, CancellationToken.None)).ReturnsAsync(_model); + List facilities = new List(); + facilities.Add(_model); + } - _ = _mocker.GetMock>() - .Setup(p => p.CreateAuditEventProducer(false)) - .Returns(Mock.Of>()); + [Fact] + public async Task TestCreateFacility() + { + SetUp(); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(new MeasureConfig()); + _mocker = new AutoMocker(); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(_serviceRegistry); + // Mock HttpMessageHandler + var handlerMock = new Mock(MockBehavior.Strict); + handlerMock + .Protected() + .Setup>( + "SendAsync", // Method to mock + ItExpr.IsAny(), // Any HttpRequestMessage + ItExpr.IsAny() // Any CancellationToken + ) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{ 'result': 'success' }"), + }); // Return the mocked response + + // Create HttpClient with mocked HttpMessageHandler + var httpClient = new HttpClient(handlerMock.Object) + { + BaseAddress = new System.Uri("http://test.com/") + }; - await _service.CreateFacility(_model,CancellationToken.None); + // Mock IFacilityConfigurationRepo + Mock _mockFacilityRepo = _mocker.GetMock(); + _mockFacilityRepo.Setup(p => p.AddAsync(_model, CancellationToken.None)).ReturnsAsync(_model); + _ = _mockFacilityRepo.Setup(p => p.FirstOrDefaultAsync(It.IsAny>>(), CancellationToken.None)).ReturnsAsync((FacilityConfigModel)null); + + // Mock IOptions + Mock> _mockServiceRegistry = _mocker.GetMock>(); + _mockServiceRegistry.Setup(p => p.Value).Returns(_serviceRegistry); + + // Mock IOptions + Mock> _mockLinkTokenServiceSettings = _mocker.GetMock>(); + _mockLinkTokenServiceSettings.Setup(p => p.Value).Returns(_linkTokenService); + + // Mock IOptions + Mock> _mockMeasureConfig = _mocker.GetMock>(); + _mockMeasureConfig.Setup(p => p.Value).Returns(_linkMeasureConfig); + + //Mock ILogger + Mock> _loggerMock = new Mock>(); + + // Mock IProducer + Mock> _producerMock = new Mock>(); + // Inject mocked dependencies into CreateAuditEventCommand + CreateAuditEventCommand _createAuditEventCommand = new CreateAuditEventCommand( + _loggerMock.Object, // Inject the mocked logger + _producerMock.Object // Inject the mocked producer + ); + + // Create FacilityConfigurationService + var _service = new FacilityConfigurationService( + _mockFacilityRepo.Object, + Mock.Of>(), + _createAuditEventCommand, + _mockServiceRegistry.Object, + _mockMeasureConfig.Object, + httpClient, + _mockLinkTokenServiceSettings.Object, + Mock.Of() + ); + + // Act + await _service.CreateFacility(_model, CancellationToken.None); + + // Assert _mocker.GetMock().Verify(p => p.AddAsync(_model, CancellationToken.None), Times.Once); - } - /* [Fact] - public async Task TestErrorCreateDuplicateFacility() - { - List scheduledTaskModels = new List(); + } - scheduledTaskModels.Add(new ScheduledTaskModel() { KafkaTopic = KafkaTopic.ReportScheduled.ToString(), ReportTypeSchedules = new List() }); - List facilities = new List(); - - _model = new FacilityConfigModel() - { - FacilityId = facilityId, - FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now - }; - _serviceRegistry = new ServiceRegistry() - { - MeasureServiceUrl = "test" - }; + [Fact] + public async Task TestErrorCreateDuplicateFacility() + { - facilities.Add(_model); + SetUp(); - _model.ScheduledTasks.AddRange(scheduledTaskModels); + facilities.Add(_model); _mocker = new AutoMocker(); - _service = _mocker.CreateInstance(); + _mocker.GetMock().Setup(p => p.AddAsync(_model, CancellationToken.None)).ReturnsAsync(_model); - _ = _mocker.GetMock() - .Setup(p => p.AddAsync(_model, CancellationToken.None)).ReturnsAsync(_model); + _mocker.GetMock().Setup(p => p.FirstOrDefaultAsync(It.IsAny>>(), CancellationToken.None)) .ReturnsAsync(_model); - _mocker.GetMock() - .Setup(p => p.FirstOrDefaultAsync(x => x.FacilityId == _model.FacilityId, CancellationToken.None)) - .Returns(Task.FromResult(_model)); + // Mock IOptions + _mocker.GetMock>().Setup(p => p.Value).Returns(new MeasureConfig()); + // Mock IFacilityConfigurationRepo + _mocker.GetMock>().Setup(p => p.Value).Returns(_serviceRegistry); - _ = _mocker.GetMock>() - .Setup(p => p.CreateAuditEventProducer(false)) - .Returns(Mock.Of>()); + // Create FacilityConfigurationService + _service = _mocker.CreateInstance(); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(new MeasureConfig()); + // Act and Assert + var result = await _service.GetFacilityByFacilityId(facilityId, CancellationToken.None); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(_serviceRegistry); + // Assert + Assert.NotNull(result); + // Act and Assert _ = await Assert.ThrowsAsync(() => _service.CreateFacility(_model, CancellationToken.None)); - }*/ + } } -} \ No newline at end of file +} diff --git a/DotNet/TenantTests/GetFacilityConfigurationTests.cs b/DotNet/TenantTests/GetFacilityConfigurationTests.cs index 1e27417d7..28fcf34ff 100644 --- a/DotNet/TenantTests/GetFacilityConfigurationTests.cs +++ b/DotNet/TenantTests/GetFacilityConfigurationTests.cs @@ -19,7 +19,6 @@ public class GetFacilityConfigurationTests private ServiceRegistry? _serviceRegistry; private const string facilityId = "TestFacility_002"; private const string facilityName = "TestFacility_002"; - private static readonly List scheduledTaskModels = new List(); private const string id = "7241D6DA-4D15-4A41-AECC-08DC4DB45323"; private AutoMocker? _mocker; @@ -35,18 +34,17 @@ public void TestGetFacility() { FacilityId = facilityId, FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now + ScheduledReports = new ScheduledReportModel() }; + _model.ScheduledReports.Daily = new string[] { "NHSNdQMAcuteCareHospitalInitialPopulation" }; + _model.ScheduledReports.Weekly = new string[] { "NHSNRespiratoryPathogenSurveillanceInitialPopulation" }; + _model.ScheduledReports.Monthly = new string[] { "NHSNGlycemicControlHypoglycemicInitialPopulation" }; _serviceRegistry = new ServiceRegistry() { MeasureServiceUrl = "test" }; - _model.ScheduledTasks.AddRange(scheduledTaskModels); - _mocker = new AutoMocker(); _service = _mocker.CreateInstance(); @@ -74,16 +72,18 @@ public void TestGetFacility() public void TestGetFacilities() { - _model = new FacilityConfigModel() { FacilityId = facilityId, FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now + ScheduledReports = new ScheduledReportModel() }; + _model.ScheduledReports.Daily = new string[] { "NHSNdQMAcuteCareHospitalInitialPopulation" }; + _model.ScheduledReports.Weekly = new string[] { "NHSNRespiratoryPathogenSurveillanceInitialPopulation" }; + _model.ScheduledReports.Monthly = new string[] { "NHSNGlycemicControlHypoglycemicInitialPopulation" }; + + _serviceRegistry = new ServiceRegistry() { MeasureServiceUrl = "test" @@ -98,8 +98,6 @@ public void TestGetFacilities() var output = (facilities: _facilities, metadata: _pagedMetaData); - _model.ScheduledTasks.AddRange(scheduledTaskModels); - _mocker = new AutoMocker(); _service = _mocker.CreateInstance(); diff --git a/DotNet/TenantTests/RemoveFacilityConfigurationTests.cs b/DotNet/TenantTests/RemoveFacilityConfigurationTests.cs index a2f7b6033..2dbf87a5f 100644 --- a/DotNet/TenantTests/RemoveFacilityConfigurationTests.cs +++ b/DotNet/TenantTests/RemoveFacilityConfigurationTests.cs @@ -39,10 +39,11 @@ public void TestRemoveFacility() Id = id, FacilityId = facilityId, FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now + ScheduledReports = new ScheduledReportModel() }; + _model.ScheduledReports.Daily = new string[] { "NHSNdQMAcuteCareHospitalInitialPopulation" }; + _model.ScheduledReports.Weekly = new string[] { "NHSNRespiratoryPathogenSurveillanceInitialPopulation" }; + _model.ScheduledReports.Monthly = new string[] { "NHSNGlycemicControlHypoglycemicInitialPopulation" }; _serviceRegistry = new ServiceRegistry() { diff --git a/DotNet/TenantTests/UpdateFacilityConfigurationTests.cs b/DotNet/TenantTests/UpdateFacilityConfigurationTests.cs index 7d1332fdb..11e7bd3d5 100644 --- a/DotNet/TenantTests/UpdateFacilityConfigurationTests.cs +++ b/DotNet/TenantTests/UpdateFacilityConfigurationTests.cs @@ -1,8 +1,5 @@ -using Confluent.Kafka; -using LantanaGroup.Link.Shared.Application.Interfaces; using LantanaGroup.Link.Shared.Application.Models; using LantanaGroup.Link.Shared.Application.Models.Configs; -using LantanaGroup.Link.Shared.Application.Models.Kafka; using LantanaGroup.Link.Tenant.Entities; using LantanaGroup.Link.Tenant.Models; using LantanaGroup.Link.Tenant.Repository.Interfaces.Sql; @@ -11,7 +8,7 @@ using Microsoft.Extensions.Options; using Moq; using Moq.AutoMock; -using static LantanaGroup.Link.Tenant.Entities.ScheduledTaskModel; +using System.Linq.Expressions; namespace TenantTests { @@ -20,65 +17,79 @@ public class UpdateFacilityConfigurationTests { private FacilityConfigModel? _model; private ServiceRegistry? _serviceRegistry; + private LinkTokenServiceSettings? _linkTokenService; + private MeasureConfig? _linkMeasureConfig; private const string facilityId = "TestFacility_002"; private const string facilityName = "TestFacility_002"; private const string id = "7241D6DA-4D15-4A41-AECC-08DC4DB45333"; private const string id1 = "7241D6DA-4D15-4A41-AECC-08DC4DB45323"; - + private List facilities = new List(); private AutoMocker? _mocker; private FacilityConfigurationService? _service; public ILogger logger = Mock.Of>(); - [Fact] - public void TestUpdateFacility() + + private void SetUp() { - List scheduledTaskModels = new List(); - scheduledTaskModels.Add(new ScheduledTaskModel() { KafkaTopic = KafkaTopic.ReportScheduled.ToString(), ReportTypeSchedules = new List() }); - List facilities = new List(); _model = new FacilityConfigModel() { Id = id, FacilityId = facilityId, FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now + ScheduledReports = new ScheduledReportModel(), + CreateDate = DateTime.Now, + ModifyDate = DateTime.Now }; + _model.ScheduledReports.Daily = new string[] { "NHSNdQMAcuteCareHospitalInitialPopulation" }; + _model.ScheduledReports.Weekly = new string[] { "NHSNRespiratoryPathogenSurveillanceInitialPopulation" }; + _model.ScheduledReports.Monthly = new string[] { "NHSNGlycemicControlHypoglycemicInitialPopulation" }; + _serviceRegistry = new ServiceRegistry() { - MeasureServiceUrl = "test" + MeasureServiceUrl = "http://localhost:5678" + }; + + _linkTokenService = new LinkTokenServiceSettings() + { + SigningKey = "test" + }; + + + _linkMeasureConfig = new MeasureConfig() + { + CheckIfMeasureExists = false, }; - _model.ScheduledTasks.AddRange(scheduledTaskModels); + List facilities = new List(); + facilities.Add(_model); + } + + + [Fact] + public void TestUpdateFacility() + { + + SetUp(); _mocker = new AutoMocker(); _service = _mocker.CreateInstance(); - _mocker.GetMock() - .Setup(p => p.UpdateAsync( _model, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.UpdateAsync(_model, CancellationToken.None)).Returns(Task.FromResult(_model)); - _mocker.GetMock() - .Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(_model)); - _mocker.GetMock() - .Setup(p => p.FirstOrDefaultAsync(x => x.FacilityId == _model.FacilityId, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.FirstOrDefaultAsync(It.IsAny>>(), CancellationToken.None)).Returns(Task.FromResult(_model)); - _mocker.GetMock>() - .Setup(p => p.CreateAuditEventProducer(false)) - .Returns(Mock.Of>()); + _mocker.GetMock>().Setup(p => p.Value).Returns(new MeasureConfig() { CheckIfMeasureExists = false }); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(new MeasureConfig()); + _mocker.GetMock>().Setup(p => p.Value).Returns(_serviceRegistry); - _mocker.GetMock>() - .Setup(p => p.Value) - .Returns(_serviceRegistry); + _mocker.GetMock>().Setup(p => p.Value).Returns(_linkTokenService); Task _updatedFacilityId = _service.UpdateFacility(id, _model, CancellationToken.None); @@ -88,96 +99,57 @@ public void TestUpdateFacility() } - [Fact] + [Fact] public async Task TestErrorUpdateNonExistingFacility() { - List scheduledTaskModels = new List(); - scheduledTaskModels.Add(new ScheduledTaskModel() { KafkaTopic = KafkaTopic.ReportScheduled.ToString(), ReportTypeSchedules = new List() }); - - _model = new FacilityConfigModel() - { - Id = id, - FacilityId = facilityId, - FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now - }; - _model.ScheduledTasks.AddRange(scheduledTaskModels); + SetUp(); _mocker = new AutoMocker(); _service = _mocker.CreateInstance(); - _ = _mocker.GetMock() - .Setup(p => p.UpdateAsync(_model, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.UpdateAsync(_model, CancellationToken.None)).Returns(Task.FromResult(_model)); - _ = _mocker.GetMock() - .Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(result: null)); + _mocker.GetMock().Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(result: null)); - _ = _mocker.GetMock() - .Setup(p => p.FirstOrDefaultAsync(x => x.FacilityId == _model.FacilityId, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.FirstOrDefaultAsync(It.IsAny>>(), CancellationToken.None)).ReturnsAsync((FacilityConfigModel)null); - _mocker.GetMock>() - .Setup(p => p.CreateAuditEventProducer(false)) - .Returns(Mock.Of>()); + _mocker.GetMock>().Setup(p => p.Value).Returns(new MeasureConfig() { CheckIfMeasureExists = false }); - Task _updatedFacilityId = _service.UpdateFacility(id, _model, CancellationToken.None); + Task _updatedFacilityId = _service.UpdateFacility(id, _model, CancellationToken.None); _mocker.GetMock().Verify(p => p.AddAsync(_model, CancellationToken.None), Times.Once); } - /* [Fact] - public async Task TestErrorDuplicateFacility() - { - List scheduledTaskModels = new List(); - - scheduledTaskModels.Add(new ScheduledTaskModel() { KafkaTopic = KafkaTopic.ReportScheduled.ToString(), ReportTypeSchedules = new List() }); - - List facilities = new List(); - - _model = new FacilityConfigModel() - { - Id = id, - FacilityId = facilityId, - FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now - }; + [Fact] + public async Task TestErrorDuplicateFacility() + { - FacilityConfigModel _modelFound = new FacilityConfigModel() - { - Id = id1, - FacilityId = facilityId, - FacilityName = facilityName, - ScheduledTasks = new List(), - MRPCreatedDate = DateTime.Now, - MRPModifyDate = DateTime.Now - }; + FacilityConfigModel _modelFound = new FacilityConfigModel() + { + Id = id1, + FacilityId = facilityId, + FacilityName = facilityName, + }; - _model.ScheduledTasks.AddRange(scheduledTaskModels); + SetUp(); - _mocker = new AutoMocker(); + facilities.Add(_model); - _service = _mocker.CreateInstance(); + _mocker = new AutoMocker(); - _mocker.GetMock() - .Setup(p => p.UpdateAsync(_model, CancellationToken.None)).Returns(Task.FromResult(_model)); + _service = _mocker.CreateInstance(); - _mocker.GetMock() - .Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(_model)); + _mocker.GetMock().Setup(p => p.UpdateAsync(_model, CancellationToken.None)).Returns(Task.FromResult(_model)); - _mocker.GetMock() - .Setup(p => p.FirstOrDefaultAsync((x => x.FacilityId == _model.FacilityId), CancellationToken.None)).Returns(Task.FromResult(_modelFound)); + _mocker.GetMock().Setup(p => p.GetAsync(id, CancellationToken.None)).Returns(Task.FromResult(_model)); - _mocker.GetMock>() - .Setup(p => p.CreateAuditEventProducer(false)) - .Returns(Mock.Of>()); + _mocker.GetMock().Setup(p => p.FirstOrDefaultAsync(It.IsAny>>(), CancellationToken.None)).ReturnsAsync(_modelFound); - _ = await Assert.ThrowsAsync(() => _service.UpdateFacility(id, _model, CancellationToken.None)); - }*/ + await Assert.ThrowsAsync(() => _service.UpdateFacility(id, _model, CancellationToken.None)); + } + } } From 287f9ca3e68893b1c7386819c5bda5cc382659ff Mon Sep 17 00:00:00 2001 From: Ariana D Mihailescu <82962995+arianamihailescu@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:54:41 -0400 Subject: [PATCH 2/5] LNK-2738: multi-measure reporting changes --- DotNet/Tenant/Entities/FacilityConfigModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DotNet/Tenant/Entities/FacilityConfigModel.cs b/DotNet/Tenant/Entities/FacilityConfigModel.cs index 95c6c1f6e..176be7614 100644 --- a/DotNet/Tenant/Entities/FacilityConfigModel.cs +++ b/DotNet/Tenant/Entities/FacilityConfigModel.cs @@ -1,8 +1,9 @@ using LantanaGroup.Link.Shared.Domain.Entities; +using System.ComponentModel.DataAnnotations.Schema; namespace LantanaGroup.Link.Tenant.Entities { - + [Table("Facilities")] public class FacilityConfigModel : BaseEntityExtended { public string FacilityId { get; set; } = null!; From b97fd0ff8cba2da3d541d926ab49a7a69a12b21b Mon Sep 17 00:00:00 2001 From: Ariana D Mihailescu <82962995+arianamihailescu@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:35:01 -0400 Subject: [PATCH 3/5] LNK-2738: multi-measure reporting changes --- DotNet/Tenant/Migrations/20240723171206_AddFacilitiesTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/DotNet/Tenant/Migrations/20240723171206_AddFacilitiesTable.cs b/DotNet/Tenant/Migrations/20240723171206_AddFacilitiesTable.cs index 872c799bb..1a7d6dbb5 100644 --- a/DotNet/Tenant/Migrations/20240723171206_AddFacilitiesTable.cs +++ b/DotNet/Tenant/Migrations/20240723171206_AddFacilitiesTable.cs @@ -11,6 +11,7 @@ public partial class AddFacilitiesTable : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable(name: "Facilities"); migrationBuilder.CreateTable( name: "Facilities", columns: table => new From aea57725066caa5b88900c440fa7041659535466 Mon Sep 17 00:00:00 2001 From: Ariana D Mihailescu <82962995+arianamihailescu@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:38:16 -0400 Subject: [PATCH 4/5] LNK-2738: multi-measure reporting changes --- DotNet/Tenant/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DotNet/Tenant/Program.cs b/DotNet/Tenant/Program.cs index 243349896..fe070e9ee 100644 --- a/DotNet/Tenant/Program.cs +++ b/DotNet/Tenant/Program.cs @@ -246,6 +246,8 @@ static void SetupMiddleware(WebApplication app) // Configure the HTTP request pipeline. app.ConfigureSwagger(); + app.AutoMigrateEF(); + app.UseRouting(); app.UseCors(CorsSettings.DefaultCorsPolicyName); From f13e587692602339386751e5a1f76879fc08b756 Mon Sep 17 00:00:00 2001 From: Ariana D Mihailescu <82962995+arianamihailescu@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:41:27 -0400 Subject: [PATCH 5/5] LNK-2738: multi-measure reporting changes --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 915659ed6..add61175c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -822,6 +822,7 @@ services: Telemetry__OtelCollectorEndpoint: http://otel-collector:4317 EnableSwagger: true Authentication__EnableAnonymousAccess: true + LinkTokenService__EnableTokenGenerationEndpoint: false ServiceRegistry__MeasureServiceUrl: http://measureeval:8067 ports: - "8074:8074" @@ -880,4 +881,4 @@ volumes: mongo_data: driver: local submission_data: - driver: local \ No newline at end of file + driver: local