+ You haven’t run any reports yet, so this section is currently empty.
+ Once you generate a report, it will appear here for you to view or download.
+
+
+
+
+
+
+ }
+
+
+
+
+
+
diff --git a/LearningHub.Nhs.WebUI/Views/Reports/_ReportPaging.cshtml b/LearningHub.Nhs.WebUI/Views/Reports/_ReportPaging.cshtml
new file mode 100644
index 000000000..5fe25a280
--- /dev/null
+++ b/LearningHub.Nhs.WebUI/Views/Reports/_ReportPaging.cshtml
@@ -0,0 +1,76 @@
+@using System.Web;
+@using LearningHub.Nhs.WebUI.Models.Learning
+@using LearningHub.Nhs.WebUI.Models.Search;
+@model LearningHub.Nhs.WebUI.Models.Report.CourseCompletionViewModel
+
+
+@{
+ var pagingModel = Model.ReportPaging;
+ var showPaging = pagingModel.CurrentPage >= 0 && pagingModel.CurrentPage <= pagingModel.TotalPages - 1;
+ var previousMessage = $"{pagingModel.CurrentPage} of {pagingModel.TotalPages}";
+ int CurrentPageNumber = pagingModel.CurrentPage + 1;
+ var nextMessage = string.Empty;
+ if (CurrentPageNumber <= pagingModel.TotalPages)
+ {
+ nextMessage = $"{CurrentPageNumber + 1} of {pagingModel.TotalPages}";
+ }
+ else
+ {
+ previousMessage = $"{CurrentPageNumber - 1} of {pagingModel.TotalPages}";
+ nextMessage = $"{CurrentPageNumber} of {pagingModel.TotalPages}";
+ }
+
+ var routeData = new Dictionary();
+ routeData["CurrentPageIndex"] = pagingModel.CurrentPage.ToString();
+ var nextRouteData = new Dictionary(routeData);
+ var previousRouteData = new Dictionary(routeData);
+ nextRouteData["ReportFormActionType"] = ReportFormActionTypeEnum.NextPageChange.ToString();
+ previousRouteData["ReportFormActionType"] = ReportFormActionTypeEnum.PreviousPageChange.ToString();
+}
+
+@if (pagingModel.TotalPages > 1)
+{
+
+}
\ No newline at end of file
diff --git a/LearningHub.Nhs.WebUI/Views/Reports/_ReportTable.cshtml b/LearningHub.Nhs.WebUI/Views/Reports/_ReportTable.cshtml
new file mode 100644
index 000000000..492abdc07
--- /dev/null
+++ b/LearningHub.Nhs.WebUI/Views/Reports/_ReportTable.cshtml
@@ -0,0 +1,145 @@
+@model LearningHub.Nhs.WebUI.Models.Report.CourseCompletionViewModel
+
+@{
+ var returnUrl = $"{Context.Request.Path}{Context.Request.QueryString}";
+}
+
+
+@if (Model.TotalCount > 0)
+{
+
}
+
@if (Context.Request.Path.Value != "/Home/Error" && !SystemOffline())
{
@if (Model.ShowHelp)
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/DatabricksConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/DatabricksConfig.cs
new file mode 100644
index 000000000..d33e0d710
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/DatabricksConfig.cs
@@ -0,0 +1,60 @@
+namespace LearningHub.Nhs.OpenApi.Models.Configuration
+{
+ ///
+ /// DatabricksConfig
+ ///
+ public class DatabricksConfig
+ {
+ ///
+ /// Gets or sets the ResourceId for the databricks instance.
+ ///
+ public string ResourceId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the base url for the databricks instance.
+ ///
+ public string InstanceUrl { get; set; } = null!;
+
+ ///
+ /// Gets or sets the warehouse id for databricks.
+ ///
+ public string WarehouseId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the job id for databricks.
+ ///
+ public string JobId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the tenant Id of the service pricncipl.
+ ///
+ public string TenantId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the client Id of the service pricncipl.
+ ///
+ public string ClientId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the client scret of the service pricncipl.
+ ///
+ public string clientSecret { get; set; } = null!;
+
+ ///
+ /// Gets or sets the endpoint to check user permission.
+ ///
+ public string UserPermissionEndpoint { get; set; } = null!;
+
+
+ ///
+ /// Gets or sets the endpoint for course completion record.
+ ///
+ public string CourseCompletionEndpoint { get; set; } = null!;
+
+ ///
+ /// Gets or sets the token.
+ ///
+ public string Token { get; set; } = null!;
+
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs
index 81b91aa65..9815f4828 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs
@@ -67,6 +67,11 @@ public class LearningHubConfig
///
public string ContentManagementQueueName { get; set; } = null!;
+ ///
+ /// Gets or sets .
+ ///
+ public string DatabricksProcessingQueueName { get; set; } = null!;
+
///
/// Gets or sets .
///
@@ -142,6 +147,15 @@ public class LearningHubConfig
///
public string BrowseCataloguesUrl { get; set; } = null!;
+ ///
+ /// Gets or sets .
+ ///
+ public string ReportUrl { get; set; } = null!;
+
+ ///
+ /// Gets or sets the StatMandId.
+ ///
+ public int StatMandId { get; set; }
///
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs
index 7b9b7b5ba..a3ea9a1bf 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs
@@ -55,5 +55,15 @@ public class NotificationSetting
/// Gets or sets the ResourceContributeAccess.
///
public string ResourceContributeAccess { get; set; } = null!;
+
+ ///
+ /// Gets or sets the report title notification content.
+ ///
+ public string ReportTitle { get; set; } = null!;
+
+ ///
+ /// Gets or sets the report notification content.
+ ///
+ public string Report { get; set; } = null!;
}
}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
index cecf1b22a..ffcc3db8b 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/DatabricksNotification.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/DatabricksNotification.cs
new file mode 100644
index 000000000..5ce62c6eb
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/DatabricksNotification.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json;
+
+namespace LearningHub.Nhs.OpenApi.Models.ViewModels
+{
+ ///
+ /// DatabricksNotification
+ ///
+ public class DatabricksNotification
+ {
+ ///
+ /// Gets or sets .
+ ///
+ [JsonProperty("event_type")]
+ public string EventType { get; set; }
+
+ ///
+ /// Gets or sets .
+ ///
+ [JsonProperty("run")]
+ public RunInfo Run { get; set; }
+
+ ///
+ /// RunInfo
+ ///
+ public class RunInfo
+ {
+ ///
+ /// Gets or sets .
+ ///
+ [JsonProperty("run_id")]
+ public long RunId { get; set; }
+
+ ///
+ /// Gets or sets .
+ ///
+ [JsonProperty("parent_run_id")]
+ public long ParentRunId { get; set; }
+ }
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs
index 912d99cae..c7ccc7862 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs
@@ -75,6 +75,11 @@ public class NavigationModel
///
public bool ShowBrowseCatalogues { get; set; }
+ ///
+ /// Gets or sets a value indicating whether to show reports.
+ ///
+ public bool ShowReports { get; set; }
+
///
/// Gets or sets a value indicating whether to show home.
///
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
index a52fd6bf1..eb117ebe3 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IReportHistoryRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IReportHistoryRepository.cs
new file mode 100644
index 000000000..b2aabcb95
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IReportHistoryRepository.cs
@@ -0,0 +1,28 @@
+namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Threading.Tasks;
+ using LearningHub.Nhs.Models.Entities;
+ using LearningHub.Nhs.Models.Entities.DatabricksReport;
+
+ ///
+ /// The ProviderRepository interface.
+ ///
+ public interface IReportHistoryRepository : IGenericRepository
+ {
+ ///
+ /// The get by id async.
+ ///
+ /// The id.
+ /// The .
+ Task GetByIdAsync(int id);
+
+ ///
+ /// The get by user id async.
+ ///
+ /// The userId.
+ /// The .
+ IQueryable GetByUserIdAsync(int userId);
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs
index 1b8fe8783..80ec18a5c 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs
@@ -6,6 +6,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework
using LearningHub.Nhs.Models.Entities;
using LearningHub.Nhs.Models.Entities.Activity;
using LearningHub.Nhs.Models.Entities.Content;
+ using LearningHub.Nhs.Models.Entities.DatabricksReport;
using LearningHub.Nhs.Models.Entities.External;
using LearningHub.Nhs.Models.Entities.Hierarchy;
using LearningHub.Nhs.Models.Entities.Messaging;
@@ -750,6 +751,11 @@ public LearningHubDbContextOptions Options
///
public virtual DbSet UserProvider { get; set; }
+ ///
+ /// Gets or sets Report History.
+ ///
+ public virtual DbSet ReportHistory { get; set; }
+
///
/// Gets or sets Resource Version Provider.
///
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs
index a571d177a..197f95dcb 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs
@@ -184,6 +184,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
// External
services.AddSingleton();
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
index 46722121a..c6d370145 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ReportHistoryMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ReportHistoryMap.cs
new file mode 100644
index 000000000..3e398744c
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ReportHistoryMap.cs
@@ -0,0 +1,23 @@
+using LearningHub.Nhs.Models.Entities.DatabricksReport;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace LearningHub.Nhs.OpenApi.Repositories.Map
+{
+ ///
+ /// The ReportHistory Map.
+ ///
+ public class ReportHistoryMap : BaseEntityMap
+ {
+ ///
+ /// The internal map.
+ ///
+ ///
+ /// The model builder.
+ ///
+ protected override void InternalMap(EntityTypeBuilder modelBuilder)
+ {
+ modelBuilder.ToTable("ReportHistory", "Reports");
+ }
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ReportHistoryRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ReportHistoryRepository.cs
new file mode 100644
index 000000000..32bfd0458
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ReportHistoryRepository.cs
@@ -0,0 +1,57 @@
+namespace LearningHub.Nhs.OpenApi.Repositories.Repositories
+{
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Linq;
+ using System.Threading.Tasks;
+ using LearningHub.Nhs.Models.Entities;
+ using LearningHub.Nhs.Models.Entities.DatabricksReport;
+ using LearningHub.Nhs.Models.Entities.Hierarchy;
+ using LearningHub.Nhs.Models.Entities.Resource;
+ using LearningHub.Nhs.OpenApi.Repositories.EntityFramework;
+ using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories;
+ using Microsoft.EntityFrameworkCore;
+
+ ///
+ /// The provider repository.
+ ///
+ public class ReportHistoryRepository : GenericRepository, IReportHistoryRepository
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The db context.
+ /// The Timezone offset manager.
+ public ReportHistoryRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager)
+ : base(dbContext, tzOffsetManager)
+ {
+ }
+
+ ///
+ public async Task GetByIdAsync(int id)
+ {
+ return await DbContext.ReportHistory.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id && !n.Deleted);
+ }
+
+ ///
+ public IQueryable GetByUserIdAsync(int userId)
+ {
+ return DbContext.ReportHistory.AsNoTracking().Where(n => n.CreateUserId == userId && !n.Deleted);
+ }
+
+ ///
+ /// The get by user id async.
+ ///
+ /// The user id.
+ /// The .
+ public IQueryable GetProvidersByUserIdAsync(int userId)
+ {
+ return DbContext.Set()
+ .Include(up => up.Provider)
+ .Where(up => up.UserId == userId && !up.Deleted).AsNoTracking()
+ .Select(up => up.Provider);
+ }
+
+
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs
index 767d35d19..37d56292d 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs
@@ -56,6 +56,7 @@ private static void AddRepositoryImplementations(this IServiceCollection service
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IDatabricksApiHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IDatabricksApiHttpClient.cs
new file mode 100644
index 000000000..f4226cd8f
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IDatabricksApiHttpClient.cs
@@ -0,0 +1,26 @@
+namespace LearningHub.Nhs.OpenApi.Services.Interface.HttpClients
+{
+ using System;
+ using System.Net.Http;
+ using System.Threading.Tasks;
+
+ ///
+ /// The Bookmark Http Client interface.
+ ///
+ public interface IDatabricksApiHttpClient : IDisposable
+ {
+ ///
+ /// GETs data from Databricks API.
+ ///
+ /// The URL to make a get call to.
+ /// Optional authorization header.
+ /// A representing the result of the asynchronous operation.
+ Task GetData(string requestUrl, string? authHeader);
+
+ ///
+ /// The Get Client method.
+ ///
+ /// The .
+ HttpClient GetClient();
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
index f6e92a8ce..ceef6ce67 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDatabricksService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDatabricksService.cs
new file mode 100644
index 000000000..10e0f358c
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDatabricksService.cs
@@ -0,0 +1,77 @@
+using LearningHub.Nhs.Models.Common;
+using LearningHub.Nhs.Models.Databricks;
+using LearningHub.Nhs.OpenApi.Models.ViewModels;
+using System.Threading.Tasks;
+
+namespace LearningHub.Nhs.OpenApi.Services.Interface.Services
+{
+ ///
+ /// IDatabricks service
+ ///
+ public interface IDatabricksService
+ {
+ ///
+ /// IsUserReporter.
+ ///
+ /// The userId.
+ /// A representing the result of the asynchronous operation.
+ Task IsUserReporter(int userId);
+
+ ///
+ /// CourseCompletionReport.
+ ///
+ /// The userId.
+ /// The model.
+ /// A representing the result of the asynchronous operation.
+ Task CourseCompletionReport(int userId, DatabricksRequestModel model);
+
+ ///
+ /// CourseCompletionReport.
+ ///
+ /// The userId.
+ /// The page.
+ /// The pageSize.
+ /// A representing the result of the asynchronous operation.
+ Task> GetPagedReportHistory(int userId, int page, int pageSize);
+
+ ///
+ /// GetPagedReportHistoryById.
+ ///
+ /// The userId.
+ /// The reportHistoryId.
+ /// A representing the result of the asynchronous operation.
+ Task GetPagedReportHistoryById(int userId, int reportHistoryId);
+
+ ///
+ /// QueueReportDownload
+ ///
+ ///
+ ///
+ ///
+ Task QueueReportDownload(int userId, int reportHistoryId);
+
+ ///
+ /// DownloadReport
+ ///
+ ///
+ ///
+ ///
+ Task DownloadReport(int userId, int reportHistoryId);
+
+ ///
+ /// DatabricksJobUpdate.
+ ///
+ ///
+ ///
+ ///
+ Task DatabricksJobUpdate(int userId, DatabricksNotification databricksNotification);
+
+ ///
+ /// DatabricksJobUpdate.
+ ///
+ /// userId.
+ /// databricksUpdateRequest.
+ ///
+ Task UpdateDatabricksReport(int userId, DatabricksUpdateRequest databricksUpdateRequest);
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs
index 0a0af21eb..217a77228 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs
@@ -69,5 +69,14 @@ public interface INotificationService
/// Error message.
/// The .
Task CreatePublishFailedNotificationAsync(int userId, string resourceTitle, string errorMessage = "");
+
+ ///
+ /// Creates report processed notification.
+ ///
+ /// The current user id.
+ /// Report Name.
+ /// Report Content.
+ /// The .
+ Task CreateReportNotificationAsync(int userId, string reportName, string reportContent);
}
}
\ No newline at end of file
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/DatabricksApiHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/DatabricksApiHttpClient.cs
new file mode 100644
index 000000000..b5452240a
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/DatabricksApiHttpClient.cs
@@ -0,0 +1,63 @@
+namespace LearningHub.Nhs.OpenApi.Services.HttpClients
+{
+ using System;
+ using System.Net.Http;
+ using System.Net.Http.Headers;
+ using System.Threading.Tasks;
+ using IdentityModel.Client;
+ using LearningHub.Nhs.OpenApi.Models.Configuration;
+ using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients;
+ using Microsoft.Extensions.Options;
+ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+
+ ///
+ /// Http client for Databricks.
+ ///
+ public class DatabricksApiHttpClient : IDatabricksApiHttpClient
+ {
+ private readonly HttpClient httpClient;
+ private readonly IOptions databricksConfig;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Configuration details for the databricks.
+ public DatabricksApiHttpClient(IOptions databricksConfig)
+ {
+ this.databricksConfig = databricksConfig;
+ this.httpClient = new HttpClient { BaseAddress = new Uri(databricksConfig.Value.InstanceUrl) };
+ this.httpClient.DefaultRequestHeaders.Accept.Clear();
+ this.httpClient.DefaultRequestHeaders.Accept.Add(
+ new MediaTypeWithQualityHeaderValue("application/json"));
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.httpClient.Dispose();
+ }
+
+ ///
+ /// The Get Client method.
+ ///
+ /// The .
+ public HttpClient GetClient()
+ {
+ string accessToken = this.databricksConfig.Value.Token;
+ this.httpClient.SetBearerToken(accessToken);
+ return this.httpClient;
+ }
+
+ ///
+ public async Task GetData(string requestUrl, string? authHeader)
+ {
+ if (!string.IsNullOrEmpty(authHeader))
+ {
+ this.httpClient.SetBearerToken(authHeader);
+ }
+
+ var message = await this.httpClient.GetAsync(requestUrl).ConfigureAwait(false);
+ return message;
+ }
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
index e2e064738..ccd7f9de7 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DatabricksService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DatabricksService.cs
new file mode 100644
index 000000000..88705817e
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DatabricksService.cs
@@ -0,0 +1,483 @@
+using LearningHub.Nhs.Models.Bookmark;
+using LearningHub.Nhs.Models.Entities.Reporting;
+using LearningHub.Nhs.OpenApi.Models.Configuration;
+using LearningHub.Nhs.OpenApi.Services.HttpClients;
+using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients;
+using LearningHub.Nhs.OpenApi.Services.Interface.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text.Json.Nodes;
+using System.Text;
+using System.Threading.Tasks;
+using LearningHub.Nhs.Models.Databricks;
+using System.Linq;
+using System.Net.Http.Headers;
+using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories;
+using LearningHub.Nhs.Models.Entities.DatabricksReport;
+using AutoMapper;
+using LearningHub.Nhs.Models.Entities.Activity;
+using LearningHub.Nhs.Models.Resource.Activity;
+using LearningHub.Nhs.Models.Common;
+using LearningHub.Nhs.Models.Notification;
+using Microsoft.EntityFrameworkCore;
+using LearningHub.Nhs.Models.Enums.Report;
+using Newtonsoft.Json.Linq;
+using LearningHub.Nhs.OpenApi.Models.ViewModels;
+using LearningHub.Nhs.Models.Resource;
+using LearningHub.Nhs.OpenApi.Repositories.Repositories;
+using LearningHub.Nhs.Models.Constants;
+using LearningHub.Nhs.Models.Hierarchy;
+using LearningHub.Nhs.Models.Enums;
+using System.Text.Json;
+using LearningHub.Nhs.Models.Entities.Resource;
+using LearningHub.Nhs.Models.Entities;
+
+namespace LearningHub.Nhs.OpenApi.Services.Services
+{
+ ///
+ /// DatabricksService
+ ///
+ public class DatabricksService : IDatabricksService
+ {
+ private const string CacheKey = "DatabricksReporter";
+ private readonly IOptions databricksConfig;
+ private readonly IOptions learningHubConfig;
+ private readonly IReportHistoryRepository reportHistoryRepository;
+ private readonly IQueueCommunicatorService queueCommunicatorService;
+ private readonly ICachingService cachingService;
+ private readonly INotificationService notificationService;
+ private readonly IUserNotificationService userNotificationService;
+ private readonly IMoodleApiService moodleApiService;
+ private readonly IMapper mapper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// databricksConfig.
+ /// learningHubConfig.
+ /// reportHistoryRepository.
+ /// mapper.
+ /// queueCommunicatorService.
+ /// cachingService.
+ /// notificationService.
+ /// userNotificationService.
+ /// moodleApiService.
+ public DatabricksService(IOptions databricksConfig,IOptions learningHubConfig, IReportHistoryRepository reportHistoryRepository, IMapper mapper, IQueueCommunicatorService queueCommunicatorService, ICachingService cachingService, INotificationService notificationService, IUserNotificationService userNotificationService, IMoodleApiService moodleApiService)
+ {
+ this.databricksConfig = databricksConfig;
+ this.learningHubConfig = learningHubConfig;
+ this.reportHistoryRepository = reportHistoryRepository;
+ this.mapper = mapper;
+ this.queueCommunicatorService = queueCommunicatorService;
+ this.cachingService = cachingService;
+ this.notificationService = notificationService;
+ this.userNotificationService = userNotificationService;
+ this.moodleApiService = moodleApiService;
+ }
+
+ ///
+ public async Task IsUserReporter(int userId)
+ {
+ string cacheKey = $"{CacheKey}_{userId}";
+ var userReportPermission = await this.cachingService.GetAsync(cacheKey);
+ if (userReportPermission.ResponseEnum == CacheReadResponseEnum.Found)
+ {
+ return userReportPermission.Item;
+ }
+
+
+ DatabricksApiHttpClient databricksInstance = new DatabricksApiHttpClient(this.databricksConfig);
+
+ var sqlText = $"CALL {this.databricksConfig.Value.UserPermissionEndpoint}({userId});";
+ const string requestUrl = "/api/2.0/sql/statements";
+
+ var requestPayload = new
+ {
+ warehouse_id = this.databricksConfig.Value.WarehouseId,
+ statement = sqlText,
+ wait_timeout = "30s",
+ on_wait_timeout = "CANCEL"
+ };
+
+ var jsonBody = JsonConvert.SerializeObject(requestPayload);
+ using var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+
+ var response = await databricksInstance.GetClient().PostAsync(requestUrl, content);
+
+ var databricksResponse = await databricksInstance.GetClient().PostAsync(requestUrl, content);
+ if (databricksResponse.StatusCode is not HttpStatusCode.OK)
+ {
+ //log failure
+ return false;
+ }
+ var responseResult = await databricksResponse.Content.ReadAsStringAsync();
+
+ responseResult = responseResult.Trim();
+ var root = JsonDocument.Parse(responseResult).RootElement;
+ string data = root.GetProperty("result").GetProperty("data_array")[0][0].GetString();
+ bool isReporter = data == "1";
+
+ await this.cachingService.SetAsync(cacheKey, isReporter);
+ return isReporter;
+ }
+
+ ///
+ public async Task CourseCompletionReport(int userId, DatabricksRequestModel model)
+ {
+ userId = 22527;
+ newEntry:
+ if (model.ReportHistoryId == 0 && model.Take > 1)
+ {
+
+ bool timePeriodCheck = int.TryParse(model.TimePeriod, out int timePeriod);
+ var reportHistory = new ReportHistory { CourseFilter = string.Join(",", model.Courses) ,StartDate = model.StartDate,EndDate =model.EndDate, PeriodDays = timePeriodCheck ? timePeriod : 0 ,
+ FirstRun = DateTimeOffset.Now, LastRun = DateTimeOffset.Now, ReportStatusId = 2};
+ model.ReportHistoryId = await AddReportHistory(userId, reportHistory);
+ }
+ else if(model.ReportHistoryId > 0 && model.Take > 1)
+ {
+ //get the existing values and compare
+ var reportChecker = await GetPagedReportHistoryById(userId, model.ReportHistoryId);
+ if (reportChecker != null)
+ {
+ if(reportChecker.CourseFilter == "all") { reportChecker.CourseFilter = string.Empty; }
+ if(reportChecker.CourseFilter != string.Join(",", model.Courses) || reportChecker.StartDate.GetValueOrDefault().Date != model.StartDate.GetValueOrDefault().Date || reportChecker.EndDate.GetValueOrDefault().Date != model.EndDate.GetValueOrDefault().Date)
+ {
+ model.ReportHistoryId = 0;
+ goto newEntry;
+ }
+ }
+ await UpdateReportLastRunTime(userId, model.ReportHistoryId);
+ }
+
+ DatabricksApiHttpClient databricksInstance = new DatabricksApiHttpClient(this.databricksConfig);
+
+ const string requestUrl = "/api/2.0/sql/statements";
+
+ var sql = $@"CALL {this.databricksConfig.Value.CourseCompletionEndpoint}(:par_adminId, :par_completionFlag, :par_locationId, :par_catalogueId, :par_learnerId, :par_courseId, :par_PageSize, :par_PageNumber, :par_Date_from, :par_Date_to);";
+
+
+ var parameters = new List>
+ {
+ new("par_adminId", userId),
+ new("par_completionFlag", -1),
+ new("par_locationId", -1),
+ new("par_catalogueId", -1),
+ new("par_learnerId", -1),
+ new("par_courseId", model.Courses.Count < 1 ? string.Empty : string.Join(",", model.Courses)),
+ new("par_PageSize", model.Take),
+ new("par_PageNumber", model.Skip),
+ new("par_Date_from", model.StartDate.HasValue ? model.StartDate.Value.ToString("yyyy-MM-dd"): string.Empty),
+ new("par_Date_to", model.EndDate.HasValue ? model.EndDate.Value.ToString("yyyy-MM-dd"): string.Empty),
+ };
+
+ var formattedParams = parameters.Select(p => new { name = p.Key, value = p.Value });
+
+ var body = new
+ {
+ warehouse_id = this.databricksConfig.Value.WarehouseId,
+ statement = sql,
+ parameters = formattedParams,
+ wait_timeout = "30s",
+ on_wait_timeout = "CANCEL"
+ };
+
+ var json = JsonConvert.SerializeObject(body);
+ using var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = await databricksInstance.GetClient().PostAsync(requestUrl, content);
+
+ var databricksResponse = await databricksInstance.GetClient().PostAsync(requestUrl, content);
+ if (databricksResponse.StatusCode is not HttpStatusCode.OK)
+ {
+ //log failure
+ return new DatabricksDetailedViewModel { ReportHistoryId = model.ReportHistoryId };
+ }
+ var responseResult = await databricksResponse.Content.ReadAsStringAsync();
+
+ var result = JsonConvert.DeserializeObject(responseResult);
+ if (result != null && result.Result.DataArray != null)
+ {
+ var records = MapDataArrayToCourseCompletionRecords(result.Result.DataArray);
+ return new DatabricksDetailedViewModel { CourseCompletionRecords = records, ReportHistoryId = model.ReportHistoryId };
+
+ }
+
+ return new DatabricksDetailedViewModel { CourseCompletionRecords= new List(), ReportHistoryId = model.ReportHistoryId };
+ }
+
+ ///
+ public async Task> GetPagedReportHistory(int userId,int page, int pageSize)
+ {
+ userId = 22527;
+ var result = new PagedResultSet();
+ var query = this.reportHistoryRepository.GetByUserIdAsync(userId);
+
+ // Execute async count
+ result.TotalItemCount = await query.CountAsync();
+ try
+ {
+ // Execute async paging
+ var pagedItems = await query
+ .OrderByDescending(x => x.LastRun)
+ .Skip((page - 1) * pageSize)
+ .Take(pageSize)
+ .ToListAsync();
+
+ result.Items = mapper.Map>(pagedItems);
+ }
+ catch(Exception e)
+ {
+
+ }
+
+ return result;
+ }
+
+ ///
+ public async Task GetPagedReportHistoryById(int userId, int reportHistoryId)
+ {
+ userId = 22527;
+ var result = new ReportHistoryModel();
+
+ var reportHistory = await this.reportHistoryRepository.GetByIdAsync(reportHistoryId);
+ if(reportHistory != null)
+ {
+ if(reportHistory.CreateUserId != userId)
+ {
+ throw new Exception("Invalid Id");
+ }
+ }
+ result = mapper.Map(reportHistory);
+ if(result != null && string.IsNullOrWhiteSpace(result.CourseFilter))
+ {
+ result.CourseFilter = "all";
+ }
+
+ return result;
+
+ }
+
+ ///
+ public async Task QueueReportDownload(int userId, int reportHistoryId)
+ {
+ userId = 22527;
+ var result = new ReportHistoryModel();
+
+ var reportHistory = await this.reportHistoryRepository.GetByIdAsync(reportHistoryId);
+ if (reportHistory != null && reportHistory.DownloadRequest == null)
+ {
+ if (reportHistory.CreateUserId != userId)
+ {
+ throw new Exception("Invalid Id");
+ }
+ reportHistory.DownloadRequest = true;
+ reportHistory.DownloadRequested = DateTimeOffset.Now;
+ }
+ else
+ {
+ throw new Exception("Invalid Id");
+ }
+ //call the job
+ DatabricksApiHttpClient databricksInstance = new DatabricksApiHttpClient(this.databricksConfig);
+
+ const string requestUrl = "/api/2.1/jobs/run-now";
+
+ var body = new
+ {
+ job_id = this.databricksConfig.Value.JobId,
+ notebook_params = new
+ {
+ par_adminId = userId,
+ par_completionFlag = -1,
+ par_locationId = -1,
+ par_catalogueId = -1,
+ par_learnerId = -1,
+ par_courseId = reportHistory.CourseFilter,
+ par_PageSize = 0,
+ par_PageNumber = 0,
+ par_Date_from = reportHistory.StartDate.GetValueOrDefault().ToString("yyyy-MM-dd"),
+ par_Date_to = reportHistory.EndDate.GetValueOrDefault().ToString("yyyy-MM-dd")
+ }
+ };
+
+ var json = JsonConvert.SerializeObject(body);
+ using var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var databricksResponse = await databricksInstance.GetClient().PostAsync(requestUrl, content);
+ if (databricksResponse.StatusCode is not HttpStatusCode.OK)
+ {
+ reportHistory.ProcessingMessage = databricksResponse.ReasonPhrase;
+ reportHistory.ReportStatusId = (int)Nhs.Models.Enums.Report.Status.Failed;
+ await reportHistoryRepository.UpdateAsync(userId, reportHistory);
+ return false;
+ }
+ var responseResult = await databricksResponse.Content.ReadAsStringAsync();
+ var responseData = JsonConvert.DeserializeObject(responseResult);
+ if (responseData != null)
+ {
+ reportHistory.ReportStatusId = (int)Nhs.Models.Enums.Report.Status.Pending;
+ reportHistory.ParentJobRunId = (long)responseData.run_id;
+ await reportHistoryRepository.UpdateAsync(userId, reportHistory);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ public async Task DownloadReport(int userId, int reportHistoryId)
+ {
+ userId = 22527;
+ var response = new ReportHistoryModel();
+
+ var reportHistory = await this.reportHistoryRepository.GetByIdAsync(reportHistoryId);
+ if (reportHistory != null)
+ {
+ if (reportHistory.CreateUserId != userId)
+ {
+ throw new Exception("Invalid Id");
+ }
+ reportHistory.DownloadedDate = DateTimeOffset.Now;
+ await reportHistoryRepository.UpdateAsync(userId, reportHistory);
+ response = mapper.Map(reportHistory);
+ }
+ else
+ {
+ throw new Exception("Invalid Id");
+ }
+
+ return response;
+ }
+
+ ///
+ /// DatabricksJobUpdate.
+ ///
+ ///
+ ///
+ ///
+ public async Task DatabricksJobUpdate(int userId, DatabricksNotification databricksNotification)
+ {
+ var reportHistory = await this.reportHistoryRepository.GetAll().FirstOrDefaultAsync(x=>x.ParentJobRunId == databricksNotification.Run.ParentRunId);
+ if (reportHistory == null) { return; }
+ reportHistory.JobRunId = databricksNotification.Run.RunId;
+ if (!databricksNotification.EventType.Contains("success"))
+ {
+ reportHistory.ReportStatusId = (int)Nhs.Models.Enums.Report.Status.Failed;
+ reportHistoryRepository.Update(userId, reportHistory);
+ return;
+ }
+ reportHistoryRepository.Update(userId, reportHistory);
+
+ await this.queueCommunicatorService.SendAsync(this.learningHubConfig.Value.DatabricksProcessingQueueName, databricksNotification.Run.RunId);
+ return;
+ }
+
+
+ ///
+ /// DatabricksJobUpdate.
+ ///
+ /// userId.
+ /// databricksUpdateRequest.
+ ///
+ public async Task UpdateDatabricksReport(int userId, DatabricksUpdateRequest databricksUpdateRequest)
+ {
+ var reportHistory = await this.reportHistoryRepository.GetAll().FirstOrDefaultAsync(x => x.JobRunId == databricksUpdateRequest.RunId);
+ if (reportHistory == null) { return; }
+ if(string.IsNullOrWhiteSpace(databricksUpdateRequest.ProcessingMessage))
+ {
+ reportHistory.DownloadReady = DateTimeOffset.Now;
+ reportHistory.FilePath = databricksUpdateRequest.FilePath;
+ reportHistory.ReportStatusId = (int)Nhs.Models.Enums.Report.Status.Ready;
+ //send notification
+ string firstCourse = string.Empty;
+
+ var courses = await moodleApiService.GetCoursesByCategoryIdAsync(learningHubConfig.Value.StatMandId);
+
+ firstCourse = string.IsNullOrWhiteSpace(reportHistory.CourseFilter)
+ ? courses.Courses.Select(c => c.Displayname).FirstOrDefault()
+ : courses.Courses
+ .Where(c => reportHistory.CourseFilter.Contains(c.Id.ToString()))
+ .Select(c => c.Displayname)
+ .FirstOrDefault();
+
+
+ var notificationId = await this.notificationService.CreateReportNotificationAsync(userId, "Course Completion", firstCourse);
+
+ if (notificationId > 0)
+ {
+ await this.userNotificationService.CreateAsync(userId, new UserNotification { UserId = reportHistory.CreateUserId, NotificationId = notificationId });
+ }
+ }
+ else
+ {
+ reportHistory.ProcessingMessage = databricksUpdateRequest.ProcessingMessage;
+ reportHistory.ReportStatusId = (int)Nhs.Models.Enums.Report.Status.Failed;
+ }
+
+ reportHistoryRepository.Update(userId, reportHistory);
+ return;
+ }
+
+
+ private async Task AddReportHistory(int userId,ReportHistory model)
+ {
+ return await reportHistoryRepository.CreateAsync(userId, model);
+ }
+
+ private async Task UpdateReportLastRunTime(int userId, int reportHistoryId)
+ {
+ var entry = await reportHistoryRepository.GetByIdAsync(reportHistoryId);
+ entry.LastRun = DateTime.Now;
+ await reportHistoryRepository.UpdateAsync(userId, entry);
+ }
+
+ ///
+ /// MapDataArrayToCourseCompletionRecords.
+ ///
+ ///
+ ///
+ public static List MapDataArrayToCourseCompletionRecords(List> dataArray)
+ {
+ var records = new List();
+
+ foreach (var row in dataArray)
+ {
+ if (row == null || row.Count < 19) continue;
+
+ var record = new DatabricksDetailedItemViewModel
+ {
+ UserName = row[0]?.ToString(),
+ FirstName = row[1]?.ToString(),
+ LastName = row[2]?.ToString(),
+ Email = row[3]?.ToString(),
+ Programme = row[4]?.ToString(),
+ Course = row[5]?.ToString(),
+ CourseStatus = row[6]?.ToString(),
+ Location = row[7]?.ToString(),
+ Role = row[8]?.ToString(),
+ Grade = row[9]?.ToString(),
+ MedicalCouncilNo = row[10]?.ToString(),
+ MedicalCouncilName = row[11]?.ToString(),
+ LastAccess = row[12]?.ToString(),
+ CourseCompletionDate = row[13]?.ToString(),
+ ReferenceType = row[14]?.ToString(),
+ ReferenceValue = row[15]?.ToString(),
+ PermissionType = row[16]?.ToString(),
+ MinValidDate = row[17]?.ToString(),
+ TotalRows = row[18] != null && int.TryParse(row[18].ToString(), out int totalRows) ? totalRows : 0
+ };
+
+ records.Add(record);
+ }
+
+ return records;
+ }
+
+ }
+
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs
index a8c6ceb78..9e28c5041 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs
@@ -13,16 +13,19 @@ public class NavigationPermissionService : INavigationPermissionService
{
private readonly IResourceService resourceService;
private readonly IUserGroupService userGroupService;
+ private readonly IDatabricksService databricksService;
///
/// Initializes a new instance of the class.
///
/// Resource service.
/// userGroup service.
- public NavigationPermissionService(IResourceService resourceService, IUserGroupService userGroupService)
+ /// databricksService.
+ public NavigationPermissionService(IResourceService resourceService, IUserGroupService userGroupService, IDatabricksService databricksService)
{
this.resourceService = resourceService;
this.userGroupService = userGroupService;
+ this.databricksService = databricksService;
}
///
@@ -86,6 +89,7 @@ public NavigationModel NotAuthenticated()
ShowSignOut = false,
ShowMyAccount = false,
ShowBrowseCatalogues = false,
+ ShowReports = false,
};
}
@@ -111,6 +115,7 @@ private NavigationModel AuthenticatedAdministrator(string controllerName)
ShowSignOut = true,
ShowMyAccount = true,
ShowBrowseCatalogues = true,
+ ShowReports = true,
};
}
@@ -137,6 +142,7 @@ private async Task AuthenticatedBlueUser(string controllerName,
ShowSignOut = true,
ShowMyAccount = true,
ShowBrowseCatalogues = true,
+ ShowReports = await this.databricksService.IsUserReporter(userId),
};
}
@@ -161,6 +167,7 @@ private NavigationModel AuthenticatedGuest()
ShowSignOut = true,
ShowMyAccount = false,
ShowBrowseCatalogues = false,
+ ShowReports = false,
};
}
@@ -186,6 +193,7 @@ private async Task AuthenticatedReadOnly(string controllerName,
ShowSignOut = true,
ShowMyAccount = false,
ShowBrowseCatalogues = true,
+ ShowReports = await this.databricksService.IsUserReporter(userId),
};
}
@@ -210,6 +218,7 @@ private async Task AuthenticatedBasicUserOnly(int userId)
ShowSignOut = true,
ShowMyAccount = true,
ShowBrowseCatalogues = true,
+ ShowReports = false,
};
}
@@ -234,6 +243,7 @@ private NavigationModel InLoginWizard()
ShowSignOut = true,
ShowMyAccount = false,
ShowBrowseCatalogues = false,
+ ShowReports = false,
};
}
}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs
index fe002695a..e001da091 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs
@@ -195,6 +195,34 @@ public async Task CreateResourcePublishedNotificationAsync(int userId, stri
}
}
+ ///
+ /// Creates report processed notification.
+ ///
+ /// The current user id.
+ /// Report Name.
+ /// Report Content.
+ /// The .
+ public async Task CreateReportNotificationAsync(int userId, string reportName, string reportContent)
+ {
+ var title = learningHubConfig.Notifications.ReportTitle.Replace("[ReportName]", reportName).Replace("[ReportContent]", reportContent);
+
+ var message = learningHubConfig.Notifications.Report.Replace("[ReportName]", reportName).Replace("[ReportContent]", reportContent);
+
+
+ var notification = await this.CreateAsync(userId, this.UserSpecificNotification(
+ title, message, NotificationTypeEnum.ReportProcessed, NotificationPriorityEnum.General));
+
+ if (notification.CreatedId.HasValue)
+ {
+ return notification.CreatedId.Value;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+
///
/// Creates resource publish failed notification.
///
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs
index 87c94f64e..870ccba05 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs
@@ -24,6 +24,7 @@ public static void AddServices(this IServiceCollection services)
{
services.AddScoped();
services.AddHttpClient();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
@@ -36,6 +37,7 @@ public static void AddServices(this IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
index 35c780917..ac9eaae79 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs
index 7d5a61795..1c5a692e3 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs
@@ -45,6 +45,11 @@ public static class ConfigurationExtensions
///
public const string MoodleSectionName = "Moodle";
+ ///
+ /// The DatabricksSectionName.
+ ///
+ public const string DatabricksSectionName = "Databricks";
+
///
/// Adds config.
///
@@ -65,6 +70,8 @@ public static void AddConfig(this IServiceCollection services, IConfiguration co
services.AddOptions().Bind(config.GetSection(AzureSectionName));
services.AddOptions().Bind(config.GetSection(MoodleSectionName));
+
+ services.AddOptions().Bind(config.GetSection(DatabricksSectionName));
}
private static OptionsBuilder RegisterPostConfigure(this OptionsBuilder builder)
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ReportController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ReportController.cs
new file mode 100644
index 000000000..faee4af05
--- /dev/null
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ReportController.cs
@@ -0,0 +1,129 @@
+namespace LearningHub.NHS.OpenAPI.Controllers
+{
+ using System.Threading.Tasks;
+ using LearningHub.Nhs.Models.Common;
+ using LearningHub.Nhs.Models.Databricks;
+ using LearningHub.Nhs.Models.Paging;
+ using LearningHub.Nhs.OpenApi.Models.ViewModels;
+ using LearningHub.Nhs.OpenApi.Services.Interface.Services;
+ using Microsoft.AspNetCore.Authorization;
+ using Microsoft.AspNetCore.Mvc;
+
+ ///
+ /// Report Controller.
+ ///
+ [ApiController]
+ [Authorize(Policy = "AuthorizeOrCallFromLH")]
+ [Route("Report")]
+ public class ReportController : OpenApiControllerBase
+ {
+ private readonly IDatabricksService databricksService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The catalogue service.
+ public ReportController(IDatabricksService databricksService)
+ {
+ this.databricksService = databricksService;
+ }
+
+ ///
+ /// Get all catalogues.
+ ///
+ /// Task.
+ [HttpGet]
+ [Route("GetReporterPermission")]
+ public async Task GetReporterPermission()
+ {
+ return await this.databricksService.IsUserReporter(this.CurrentUserId.GetValueOrDefault());
+ }
+
+ ///
+ /// Get CourseCompletionReport from Databricks.
+ ///
+ /// requestModel.
+ /// Task.
+ [HttpPost]
+ [Route("GetCourseCompletionReport")]
+ public async Task CourseCompletionReport(DatabricksRequestModel requestModel)
+ {
+ return await this.databricksService.CourseCompletionReport(this.CurrentUserId.GetValueOrDefault(),requestModel);
+ }
+
+ ///
+ /// Get CourseCompletionReport from Databricks.
+ ///
+ /// request.
+ /// Task.
+ [HttpPost]
+ [Route("GetReportHistory")]
+ public async Task> GetReportHistory(PagingRequestModel request)
+ {
+ return await this.databricksService.GetPagedReportHistory(this.CurrentUserId.GetValueOrDefault(), request.Page, request.PageSize);
+ }
+
+ ///
+ /// Get GetReportHistoryById.
+ ///
+ /// reportHistoryId.
+ /// Task.
+ [HttpGet]
+ [Route("GetReportHistoryById/{reportHistoryId}")]
+ public async Task GetReportHistoryById(int reportHistoryId)
+ {
+ return await this.databricksService.GetPagedReportHistoryById(this.CurrentUserId.GetValueOrDefault(), reportHistoryId);
+ }
+
+ ///
+ /// Get QueueReportDownload.
+ ///
+ /// reportHistoryId.
+ /// Task.
+ [HttpGet]
+ [Route("QueueReportDownload/{reportHistoryId}")]
+ public async Task QueueReportDownload(int reportHistoryId)
+ {
+ return await this.databricksService.QueueReportDownload(this.CurrentUserId.GetValueOrDefault(), reportHistoryId);
+ }
+
+ ///
+ /// Get DownloadReport.
+ ///
+ /// reportHistoryId.
+ /// Task.
+ [HttpGet]
+ [Route("DownloadReport/{reportHistoryId}")]
+ public async Task DownloadReport(int reportHistoryId)
+ {
+ return await this.databricksService.DownloadReport(this.CurrentUserId.GetValueOrDefault(), reportHistoryId);
+ }
+
+ ///
+ /// DatabricksJobNotify.
+ ///
+ /// databricksNotification.
+ /// Task.
+ [HttpPost]
+ [AllowAnonymous]
+ [Route("DatabricksJobNotify")]
+ public async Task DatabricksJobNotify(DatabricksNotification databricksNotification)
+ {
+ await this.databricksService.DatabricksJobUpdate(this.CurrentUserId.GetValueOrDefault(), databricksNotification);
+ return this.Ok(new ApiResponse(true));
+ }
+
+ ///
+ /// UpdateDatabricksReport.
+ ///
+ /// databricksUpdateRequest.
+ /// Task.
+ [HttpPost]
+ [Route("UpdateDatabricksReport")]
+ public async Task UpdateDatabricksReport(DatabricksUpdateRequest databricksUpdateRequest)
+ {
+ await this.databricksService.UpdateDatabricksReport(this.CurrentUserId.GetValueOrDefault(), databricksUpdateRequest);
+ return this.Ok(new ApiResponse(true));
+ }
+ }
+}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs
index 98590761f..54d043086 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs
@@ -353,7 +353,6 @@ public async Task>> GetLHUserNavigation()
return this.MenuItems(model);
}
-
private List> MenuItems(NavigationModel model)
{
var menu = new List>
@@ -382,6 +381,12 @@ private List> MenuItems(NavigationModel model)
{ "url", this.learningHubConfig.BrowseCataloguesUrl },
{ "visible", model.ShowBrowseCatalogues },
},
+ new Dictionary
+ {
+ { "title", "Reports" },
+ { "url", this.learningHubConfig.ReportUrl },
+ { "visible", model.ShowReports },
+ },
new Dictionary
{
{ "title", "Admin" },
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
index f23f74ba3..34579b31b 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user
index 5bdd12166..1d8db97aa 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user
@@ -5,7 +5,7 @@
IIS Local
- ApiControllerEmptyScaffolder
- root/Common/Api
+ MvcControllerEmptyScaffolder
+ root/Common/MVC/Controller
\ No newline at end of file
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
index bec2f2309..4d62b872c 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
@@ -80,6 +80,7 @@
"ResourcePublishQueueRouteName": "",
"HierarchyEditPublishQueueName": "",
"ContentManagementQueueName": "",
+ "DatabricksProcessingQueueName": "",
"AuthClientIdentityKey": "",
"LHClientIdentityKey": "",
"ReportApiClientIdentityKey": "",
@@ -93,7 +94,9 @@
"ResourcePublishFailedWithReason": "
The resource you contributed failed to publish, which means that users cannot access it.
The error message generated was: [ErrorMessage]
Please contact the support team for more information.
",
"ResourceAccessTitle": "What you can do in the Learning Hub has changed",
"ResourceReadonlyAccess": "
You can continue to search for and access resources in the Learning Hub, however you cannot contribute to it.
If you have any questions about this, please contact the support team.
",
- "ResourceContributeAccess": "
You can now contribute to the Learning Hub and continue to search for and access resources.
If you have any questions about this, please contact the support team.
"
+ "ResourceContributeAccess": "
You can now contribute to the Learning Hub and continue to search for and access resources.
If you have any questions about this, please contact the support team.
",
+ "ReportTitle": "
[ReportName] report for [ReportContent] is ready
",
+ "Report": "
Content: Your report [ReportName] report for [ReportContent] is ready. You can view and download the report in the Reports Section section
"
},
"MyContributionsUrl": "/my-contributions",
"MyLearningUrl": "/MyLearning",
@@ -107,7 +110,8 @@
"RegisterUrl": "/register",
"SignOutUrl": "/home/logout",
"MyAccountUrl": "/myaccount",
- "BrowseCataloguesUrl": "/allcatalogue"
+ "BrowseCataloguesUrl": "/allcatalogue",
+ "ReportUrl": ""
},
"LearningHubAPIConfig": {
"BaseUrl": "https://learninghub.nhs.uk/api"
@@ -126,5 +130,13 @@
"ApiBaseUrl": "",
"ApiWsRestFormat": "json",
"ApiWsToken": ""
+ },
+ "Databricks": {
+ "InstanceUrl": "",
+ "Token": "",
+ "WarehouseId": "",
+ "JobId": "",
+ "UserPermissionEndpoint": "",
+ "CourseCompletionEndpoint": ""
}
}
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index a88a4bcff..1c0d8ebee 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -103,6 +103,7 @@
+
@@ -691,6 +692,7 @@
+
diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Databricks/ReportHistory.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Databricks/ReportHistory.sql
new file mode 100644
index 000000000..61c2cec72
--- /dev/null
+++ b/WebAPI/LearningHub.Nhs.Database/Tables/Databricks/ReportHistory.sql
@@ -0,0 +1,25 @@
+CREATE TABLE [reports].[ReportHistory]
+(
+ [Id] INT NOT NULL IDENTITY (1, 1),
+ [CourseFilter] NVARCHAR(512) NULL,
+ [FirstRun] DATETIMEOFFSET(7) NOT NULL,
+ [LastRun] DATETIMEOFFSET(7) NOT NULL,
+ [PeriodDays] INT NOT NULL,
+ [StartDate][datetimeoffset](7) NULL,
+ [EndDate][datetimeoffset](7) NULL,
+ [DownloadRequest] BIT NULL,
+ [DownloadRequested][datetimeoffset](7) NULL,
+ [DownloadReady][datetimeoffset](7) NULL,
+ [ReportStatusId] INT NULL,
+ [FilePath] NVARCHAR(1024) NULL,
+ [DownloadedDate] [datetimeoffset](7) NULL,
+ [ParentJobRunId] INT NULL,
+ [JobRunId] INT NULL,
+ [ProcessingMessage] NVARCHAR(1024) NULL,
+ [Deleted] [bit] NOT NULL,
+ [CreateUserId] INT NOT NULL,
+ [CreateDate] [datetimeoffset](7) NOT NULL,
+ [AmendUserId] INT NOT NULL,
+ [AmendDate] [datetimeoffset](7) NOT NULL,
+ CONSTRAINT [PK_Reports_ReportHistory] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
+);
\ No newline at end of file
diff --git a/replacements.txt b/replacements.txt
new file mode 100644
index 000000000..4819f42b1
--- /dev/null
+++ b/replacements.txt
@@ -0,0 +1 @@
+Databricks_API_Token==>REMOVED
\ No newline at end of file
From 7af543deeb12c8afb6f43fa2ab3891c188784d14 Mon Sep 17 00:00:00 2001
From: Tobi Awe
Date: Wed, 26 Nov 2025 11:00:06 +0000
Subject: [PATCH 02/16] LH model Update
---
.../LearningHub.Nhs.WebUI.AutomatedUiTests.csproj | 2 +-
LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +-
.../LearningHub.Nhs.OpenApi.Models.csproj | 4 ++--
.../LearningHub.Nhs.OpenApi.Repositories.Interface.csproj | 2 +-
.../LearningHub.Nhs.OpenApi.Repositories.csproj | 2 +-
.../LearningHub.Nhs.OpenApi.Services.Interface.csproj | 2 +-
.../LearningHub.Nhs.OpenApi.Services.csproj | 2 +-
.../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +-
.../LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj | 2 +-
9 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj
index 2f201378f..f474d537d 100644
--- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj
+++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj
index 09cb454c3..adada3f14 100644
--- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj
+++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj
@@ -113,7 +113,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
index ffcc3db8b..2991f677e 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj
@@ -1,4 +1,4 @@
-
+net8.0
@@ -16,7 +16,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
index eb117ebe3..e7e923978 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
index c6d370145..0ee6fa49a 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
index ceef6ce67..99e4fe969 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
index ccd7f9de7..7c534adfc 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
index ac9eaae79..3ecb4d1a5 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
index 34579b31b..cfc4572c1 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj
@@ -19,7 +19,7 @@
-
+
From 0029c3b9d50743250c6c0d9bbd264ed5f3d0c545 Mon Sep 17 00:00:00 2001
From: Sarathlal Sarangadharan
Date: Wed, 26 Nov 2025 16:47:06 +0000
Subject: [PATCH 03/16] TD-6148: Hotfix changes and production release changes
---
.../LearningHub.Nhs.Database.sqlproj | 2 +-
.../Scripts/TD-6148-ADFtableData.sql | 4 +-
.../Adf/AdfMergeUserAdminLocation.sql | 62 +++++++++++--------
.../Adf/AdfMergeUserEmploymentReference.sql | 4 +-
.../Adf/AdfMergeUserGroupReporter.sql | 50 +++++++++++++++
.../Adf/AdfMergeUserHistoryAttribute.sql | 62 -------------------
.../AdfMergeUserPasswordValidationToke.sql | 4 ++
.../Adf/AdfMergeattribute.sql | 24 +++----
.../Adf/AdfMergeuserEmployment.sql | 4 ++
9 files changed, 112 insertions(+), 104 deletions(-)
create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserGroupReporter.sql
delete mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserHistoryAttribute.sql
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index a88a4bcff..25d4384fe 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -680,7 +680,6 @@
-
@@ -691,6 +690,7 @@
+
diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6148-ADFtableData.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6148-ADFtableData.sql
index 1c244876e..08003503f 100644
--- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6148-ADFtableData.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6148-ADFtableData.sql
@@ -1,5 +1,6 @@
INSERT INTO ADFSyncMetadata (SyncDirection, TableName, LastSyncTime)
VALUES
+('ELFHtoLH', 'employmentReferenceTypeTBL', '1900-01-01'),
('ELFHtoLH', 'mergeUserTBL', '1900-01-01'),
('ELFHtoLH', 'userAdminLocationTBL', '1900-01-01'),
('ELFHtoLH', 'userEmploymentReferenceTBL', '1900-01-01'),
@@ -41,4 +42,5 @@ VALUES
('ELFHtoLH', 'JobRoleTbl', '1900-01-01'),
('ELFHtoLH', 'userTermsAndConditionsTBL', '1900-01-01'),
('ELFHtoLH', 'medicalCouncilTBL', '1900-01-01'),
-('ELFHtoLH', 'staffGroupTBL', '1900-01-01')
+('ELFHtoLH', 'staffGroupTBL', '1900-01-01'),
+('ELFHtoLH', 'usergroupreportertbl', '1900-01-01')
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserAdminLocation.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserAdminLocation.sql
index 28e48027f..5128603f7 100644
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserAdminLocation.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserAdminLocation.sql
@@ -7,44 +7,56 @@
--
-- 04-11-2025 Sarathlal Initial Revision
-------------------------------------------------------------------------------
-CREATE PROCEDURE [AdfMergeUserAdminLocation]
+CREATE PROCEDURE [dbo].[AdfMergeUserAdminLocation]
@UserAdminLocationList dbo.UserAdminLocationType READONLY
AS
BEGIN
SET NOCOUNT ON;
- MERGE [elfh].[userAdminLocationTBL] AS target
- USING @UserAdminLocationList AS source
- ON target.[userId] = source.[userId]
- AND target.[adminLocationId] = source.[adminLocationId] -- composite key match
+ -- Deduplicate source first
+ ;WITH DedupedSource AS (
+ SELECT *,
+ ROW_NUMBER() OVER (
+ PARTITION BY userId, adminLocationId, deleted
+ ORDER BY amendDate DESC, createdDate DESC
+ ) AS rn
+ FROM @UserAdminLocationList
+ ),
+ CleanSource AS (
+ SELECT * FROM DedupedSource WHERE rn = 1
+ )
+
+ MERGE elfh.userAdminLocationTBL AS target
+ USING CleanSource AS source
+ ON target.userId = source.userId
+ AND target.adminLocationId = source.adminLocationId
+ AND target.deleted = source.deleted -- IMPORTANT!
WHEN MATCHED THEN
UPDATE SET
- target.[deleted] = source.[deleted],
- target.[amendUserId] = source.[amendUserId],
- target.[amendDate] = source.[amendDate],
- target.[createdUserId] = source.[createdUserId],
- target.[createdDate] = source.[createdDate]
+ target.amendUserId = source.amendUserId,
+ target.amendDate = source.amendDate,
+ target.createdUserId = source.createdUserId,
+ target.createdDate = source.createdDate
WHEN NOT MATCHED BY TARGET THEN
INSERT (
- [userId],
- [adminLocationId],
- [deleted],
- [amendUserId],
- [amendDate],
- [createdUserId],
- [createdDate]
+ userId,
+ adminLocationId,
+ deleted,
+ amendUserId,
+ amendDate,
+ createdUserId,
+ createdDate
)
VALUES (
- source.[userId],
- source.[adminLocationId],
- source.[deleted],
- source.[amendUserId],
- source.[amendDate],
- source.[createdUserId],
- source.[createdDate]
+ source.userId,
+ source.adminLocationId,
+ source.deleted,
+ source.amendUserId,
+ source.amendDate,
+ source.createdUserId,
+ source.createdDate
);
-
END
GO
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserEmploymentReference.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserEmploymentReference.sql
index 5a5aa29a6..e6f0d2ad3 100644
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserEmploymentReference.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserEmploymentReference.sql
@@ -7,13 +7,14 @@
--
-- 04-11-2025 Sarathlal Initial Revision
-------------------------------------------------------------------------------
-CREATE PROCEDURE [AdfMergeUserEmploymentReference]
+CREATE PROCEDURE [dbo].[AdfMergeUserEmploymentReference]
@UserEmploymentReferenceList dbo.UserEmploymentReferenceType READONLY
AS
BEGIN
SET NOCOUNT ON;
SET IDENTITY_INSERT [elfh].[userEmploymentReferenceTBL] ON;
+ ALTER TABLE [elfh].[userEmploymentReferenceTBL] NOCHECK CONSTRAINT ALL;
MERGE [elfh].[userEmploymentReferenceTBL] AS target
USING @UserEmploymentReferenceList AS source
ON target.[userEmploymentReferenceId] = source.[userEmploymentReferenceId]
@@ -47,5 +48,6 @@ BEGIN
source.[amendDate]
);
SET IDENTITY_INSERT [elfh].[userEmploymentReferenceTBL] OFF;
+ ALTER TABLE [elfh].[userEmploymentReferenceTBL] NOCHECK CONSTRAINT ALL;
END
GO
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserGroupReporter.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserGroupReporter.sql
new file mode 100644
index 000000000..d1817a389
--- /dev/null
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserGroupReporter.sql
@@ -0,0 +1,50 @@
+-------------------------------------------------------------------------------
+-- Author Sarathlal
+-- Created 25-11-2025
+-- Purpose ELFH-LH Data sync
+--
+-- Modification History
+--
+-- 25-11-2025 Sarathlal Initial Revision
+-------------------------------------------------------------------------------
+CREATE PROCEDURE [dbo].[AdfMergeUserGroupReporter]
+ @UserGroupReporterList [dbo].[UserGroupReporterType] READONLY
+AS
+BEGIN
+ SET NOCOUNT ON;
+ SET IDENTITY_INSERT [elfh].[userGroupReporterTBL] ON;
+ ALTER TABLE elfh.userGroupReporterTBL NOCHECK CONSTRAINT FK_userGroupReporterTBL_userGroupTBL;
+ MERGE [elfh].[userGroupReporterTBL] AS target
+ USING @UserGroupReporterList AS source
+ ON target.[userGroupReporterId] = source.[userGroupReporterId]
+
+ WHEN MATCHED THEN
+ UPDATE SET
+ target.[userId] = source.[userId],
+ target.[userGroupId] = source.[userGroupId],
+ target.[deleted] = source.[deleted],
+ target.[amendUserId] = source.[amendUserId],
+ target.[amendDate] = source.[amendDate]
+
+ WHEN NOT MATCHED BY TARGET THEN
+ INSERT (
+ [userGroupReporterId],
+ [userId],
+ [userGroupId],
+ [deleted],
+ [amendUserId],
+ [amendDate]
+ )
+ VALUES (
+ source.[userGroupReporterId],
+ source.[userId],
+ source.[userGroupId],
+ source.[deleted],
+ source.[amendUserId],
+ source.[amendDate]
+ );
+ SET IDENTITY_INSERT [elfh].[userGroupReporterTBL] OFF;
+ ALTER TABLE elfh.userGroupReporterTBL CHECK CONSTRAINT FK_userGroupReporterTBL_userGroupTBL;
+END
+GO
+
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserHistoryAttribute.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserHistoryAttribute.sql
deleted file mode 100644
index cc17b95fa..000000000
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserHistoryAttribute.sql
+++ /dev/null
@@ -1,62 +0,0 @@
--------------------------------------------------------------------------------
--- Author Sarathlal
--- Created 04-11-2025
--- Purpose ELFH-LH Data sync
---
--- Modification History
---
--- 04-11-2025 Sarathlal Initial Revision
--------------------------------------------------------------------------------
-CREATE PROCEDURE [AdfMergeUserHistoryAttribute]
- @UserHistoryAttributeList dbo.UserHistoryAttributeType READONLY
-AS
-BEGIN
- SET NOCOUNT ON;
-
- ALTER TABLE [elfh].[userHistoryAttributeTBL] NOCHECK CONSTRAINT ALL;
- SET IDENTITY_INSERT [elfh].[userHistoryAttributeTBL] ON;
- MERGE [elfh].[userHistoryAttributeTBL] AS target
- USING @UserHistoryAttributeList AS source
- ON target.[userHistoryAttributeId] = source.[userHistoryAttributeId]
-
- WHEN MATCHED THEN
- UPDATE SET
- target.[userHistoryId] = source.[userHistoryId],
- target.[attributeId] = source.[attributeId],
- target.[intValue] = source.[intValue],
- target.[textValue] = source.[textValue],
- target.[booleanValue] = source.[booleanValue],
- target.[dateValue] = source.[dateValue],
- target.[deleted] = source.[deleted],
- target.[amendUserId] = source.[amendUserId],
- target.[amendDate] = source.[amendDate]
-
- WHEN NOT MATCHED BY TARGET THEN
- INSERT (
- [userHistoryAttributeId],
- [userHistoryId],
- [attributeId],
- [intValue],
- [textValue],
- [booleanValue],
- [dateValue],
- [deleted],
- [amendUserId],
- [amendDate]
- )
- VALUES (
- source.[userHistoryAttributeId],
- source.[userHistoryId],
- source.[attributeId],
- source.[intValue],
- source.[textValue],
- source.[booleanValue],
- source.[dateValue],
- source.[deleted],
- source.[amendUserId],
- source.[amendDate]
- );
- SET IDENTITY_INSERT [elfh].[userHistoryAttributeTBL] OFF;
- ALTER TABLE [elfh].[userHistoryAttributeTBL] CHECK CONSTRAINT ALL;
-END
-GO
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserPasswordValidationToke.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserPasswordValidationToke.sql
index d9c65fd6d..c37cbd567 100644
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserPasswordValidationToke.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeUserPasswordValidationToke.sql
@@ -15,6 +15,8 @@ BEGIN
-- Enable identity insert if userPasswordValidationTokenId is an IDENTITY column
SET IDENTITY_INSERT [elfh].[userPasswordValidationTokenTBL] ON;
+ ALTER TABLE [elfh].[userPasswordValidationTokenTBL] NOCHECK CONSTRAINT ALL;
+ ALTER TABLE [hub].[User] NOCHECK CONSTRAINT ALL;
MERGE [elfh].[userPasswordValidationTokenTBL] AS target
USING @userPasswordValidationTokenList AS source
ON target.userPasswordValidationTokenId = source.userPasswordValidationTokenId
@@ -56,5 +58,7 @@ BEGIN
-- Disable identity insert
SET IDENTITY_INSERT [elfh].[userPasswordValidationTokenTBL] OFF;
+ ALTER TABLE [hub].[User] CHECK CONSTRAINT ALL;
+ ALTER TABLE [elfh].[userPasswordValidationTokenTBL] CHECK CONSTRAINT ALL;
END
GO
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeattribute.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeattribute.sql
index d6851b39d..33969535a 100644
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeattribute.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeattribute.sql
@@ -32,20 +32,6 @@ CREATE TYPE dbo.UserReportingUserType AS TABLE
[AmendDate] DATETIMEOFFSET
);
GO
-CREATE TYPE UserHistoryAttributeType AS TABLE
-(
- [userHistoryAttributeId] INT,
- [userHistoryId] INT,
- [attributeId] INT,
- [intValue] INT NULL,
- [textValue] NVARCHAR(1000) NULL,
- [booleanValue] BIT NULL,
- [dateValue] DATETIMEOFFSET NULL,
- [deleted] BIT,
- [amendUserId] INT,
- [amendDate] DATETIMEOFFSET
-);
-GO
CREATE TYPE UserEmploymentResponsibilityType AS TABLE
(
[userEmploymentResponsibilityId] INT,
@@ -584,6 +570,16 @@ CREATE TYPE dbo.MedicalCouncil AS TABLE
amendDate datetimeoffset(7)
);
GO
+CREATE TYPE [dbo].[UserGroupReporterType] AS TABLE
+(
+ [userGroupReporterId] INT,
+ [userId] INT,
+ [userGroupId] INT,
+ [deleted] BIT,
+ [amendUserId] INT,
+ [amendDate] DATETIMEOFFSET(7)
+);
+GO
CREATE PROCEDURE [dbo].[AdfMergeattribute]
@attributeList dbo.Attribute READONLY -- Table-valued parameter
AS
diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeuserEmployment.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeuserEmployment.sql
index 7e4dc75e7..5be94cd10 100644
--- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeuserEmployment.sql
+++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeuserEmployment.sql
@@ -14,6 +14,8 @@ BEGIN
SET NOCOUNT ON;
SET IDENTITY_INSERT [elfh].[userEmploymentTBL] ON;
+ ALTER TABLE [elfh].[userEmploymentTBL] NOCHECK CONSTRAINT ALL;
+ ALTER TABLE [hub].[User] NOCHECK CONSTRAINT ALL;
MERGE [elfh].[userEmploymentTBL] AS target
USING @userEmploymentList AS source
ON target.userEmploymentId = source.userEmploymentId
@@ -73,5 +75,7 @@ BEGIN
-- Disable identity insert
SET IDENTITY_INSERT [elfh].[userEmploymentTBL] OFF;
+ ALTER TABLE [elfh].[userEmploymentTBL] NOCHECK CONSTRAINT ALL;
+ ALTER TABLE [hub].[User] NOCHECK CONSTRAINT ALL;
END
GO
From 76e5329a3852cadf2381f71a4082a6e66ab8dd81 Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 07:47:46 +0000
Subject: [PATCH 04/16] TD06625
---
.../LearningHub.Nhs.Database.sqlproj | 3 +-
.../Scripts/TD-6625-AddtionalTable&CDC.sql | 10 +++++
.../Tables/Elfh/UserGroupReporterTBL.sql | 30 ++++++++++++++
.../Tables/Elfh/UserHistoryAttributeTBL.sql | 39 -------------------
4 files changed, 42 insertions(+), 40 deletions(-)
create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql
create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserGroupReporterTBL.sql
delete mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserHistoryAttributeTBL.sql
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index 1c0d8ebee..7958c3547 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -210,6 +210,7 @@
+
@@ -578,7 +579,6 @@
-
@@ -693,6 +693,7 @@
+
diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql
new file mode 100644
index 000000000..f87485a2e
--- /dev/null
+++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql
@@ -0,0 +1,10 @@
+-- DROP NodeEditor table
+DROP TABLE elfh.userHistoryAttributeTBL
+
+--Enable CDC
+EXEC sys.sp_cdc_enable_table
+ @source_schema = N'elfh',
+ @source_name = N'userReportingUserTBL',
+ @role_name = NULL,
+ @supports_net_changes = 0;
+GO
diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserGroupReporterTBL.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserGroupReporterTBL.sql
new file mode 100644
index 000000000..8794a0a64
--- /dev/null
+++ b/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserGroupReporterTBL.sql
@@ -0,0 +1,30 @@
+CREATE TABLE [elfh].[userGroupReporterTBL](
+ [userGroupReporterId] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
+ [userId] [int] NOT NULL,
+ [userGroupId] [int] NOT NULL,
+ [deleted] [bit] NOT NULL,
+ [amendUserId] [int] NOT NULL,
+ [amendDate] [datetimeoffset](7) NOT NULL,
+ CONSTRAINT [PK_userGroupReporterTBL] PRIMARY KEY CLUSTERED
+(
+ [userGroupReporterId] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY]
+) ON [PRIMARY]
+GO
+
+ALTER TABLE [elfh].[userGroupReporterTBL] ADD DEFAULT (sysdatetimeoffset()) FOR [amendDate]
+GO
+
+ALTER TABLE [elfh].[userGroupReporterTBL] WITH CHECK ADD CONSTRAINT [FK_userGroupReporterTBL_userGroupTBL] FOREIGN KEY([userGroupId])
+REFERENCES [hub].[userGroup] ([Id])
+GO
+
+ALTER TABLE [elfh].[userGroupReporterTBL] CHECK CONSTRAINT [FK_userGroupReporterTBL_userGroupTBL]
+GO
+
+ALTER TABLE [elfh].[userGroupReporterTBL] WITH CHECK ADD CONSTRAINT [FK_userGroupReporterTBL_userTBL] FOREIGN KEY([userId])
+REFERENCES [hub].[user] ([Id])
+GO
+
+ALTER TABLE [elfh].[userGroupReporterTBL] CHECK CONSTRAINT [FK_userGroupReporterTBL_userTBL]
+GO
\ No newline at end of file
diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserHistoryAttributeTBL.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserHistoryAttributeTBL.sql
deleted file mode 100644
index 0ab661329..000000000
--- a/WebAPI/LearningHub.Nhs.Database/Tables/Elfh/UserHistoryAttributeTBL.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-CREATE TABLE [elfh].[userHistoryAttributeTBL](
- [userHistoryAttributeId] [int] IDENTITY(1,1) NOT NULL,
- [userHistoryId] [int] NOT NULL,
- [attributeId] [int] NOT NULL,
- [intValue] [int] NULL,
- [textValue] [nvarchar](1000) NULL,
- [booleanValue] [bit] NULL,
- [dateValue] [datetimeoffset](7) NULL,
- [deleted] [bit] NOT NULL,
- [amendUserId] [int] NOT NULL,
- [amendDate] [datetimeoffset](7) NOT NULL,
- CONSTRAINT [PK_userHistoryAttributeTBL] PRIMARY KEY CLUSTERED
-(
- [userHistoryAttributeId] ASC
-)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
-) ON [PRIMARY]
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] ADD CONSTRAINT [DF_userHistoryAttributeTBL_deleted] DEFAULT ((0)) FOR [deleted]
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] ADD CONSTRAINT [DF_userHistoryAttributeTBL_amendDate] DEFAULT (sysdatetimeoffset()) FOR [amendDate]
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] WITH CHECK ADD CONSTRAINT [FK_userHistoryAttributeTBL_attributeId] FOREIGN KEY([attributeId])
-REFERENCES [elfh].[attributeTBL] ([attributeId])
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] CHECK CONSTRAINT [FK_userHistoryAttributeTBL_attributeId]
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] WITH CHECK ADD CONSTRAINT [FK_userHistoryAttributeTBL_userHistoryId] FOREIGN KEY([userHistoryId])
-REFERENCES [elfh].[userHistoryTBL] ([userHistoryId])
-GO
-
-ALTER TABLE [elfh].[userHistoryAttributeTBL] CHECK CONSTRAINT [FK_userHistoryAttributeTBL_userHistoryId]
-GO
-
-
From 84cdfe5484693b5e7708a2f1a3ee10448016a26d Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 07:51:41 +0000
Subject: [PATCH 05/16] scriptname update
---
...6625-AddtionalTable&CDC.sql => TD-6625-AddtionalTable-CDC.sql} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/{TD-6625-AddtionalTable&CDC.sql => TD-6625-AddtionalTable-CDC.sql} (100%)
diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable-CDC.sql
similarity index 100%
rename from WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable&CDC.sql
rename to WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-6625-AddtionalTable-CDC.sql
From 71a6dab1c3f72c7f0c5ab22b19e9cbbd9ce81042 Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 07:52:53 +0000
Subject: [PATCH 06/16] fixes
---
WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 1 -
1 file changed, 1 deletion(-)
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index 7958c3547..d63f15b59 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -210,7 +210,6 @@
-
From cad78f8baf180e6a36d67e9be6f4b9eefbc397cd Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 07:54:30 +0000
Subject: [PATCH 07/16] include-script
---
WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index d63f15b59..d093163ef 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -693,6 +693,7 @@
+
From 0a79d6d2efc76ae67fd31d19d3139c45abb30165 Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 07:58:11 +0000
Subject: [PATCH 08/16] Update LearningHub.Nhs.Database.sqlproj
---
.../LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index d093163ef..8b8de517f 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -693,7 +693,7 @@
-
+
@@ -764,4 +764,4 @@
-
\ No newline at end of file
+
From 4668c06b53bcac1a17428d03ababcda067de38b6 Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Thu, 27 Nov 2025 08:00:30 +0000
Subject: [PATCH 09/16] Update LearningHub.Nhs.Database.sqlproj
---
WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 1 -
1 file changed, 1 deletion(-)
diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
index 8b8de517f..3af7bc0f1 100644
--- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
+++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj
@@ -622,7 +622,6 @@
-
From 30969e4c0a7601b23c00cba03cea07bbbc37032a Mon Sep 17 00:00:00 2001
From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com>
Date: Fri, 28 Nov 2025 07:35:50 +0000
Subject: [PATCH 10/16] TD-6401
---
.../Views/Policies/AcceptableUsePolicy.cshtml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/LearningHub.Nhs.WebUI/Views/Policies/AcceptableUsePolicy.cshtml b/LearningHub.Nhs.WebUI/Views/Policies/AcceptableUsePolicy.cshtml
index 1b37e2979..506bfd4e7 100644
--- a/LearningHub.Nhs.WebUI/Views/Policies/AcceptableUsePolicy.cshtml
+++ b/LearningHub.Nhs.WebUI/Views/Policies/AcceptableUsePolicy.cshtml
@@ -92,7 +92,8 @@
4.5.22 contain harmful material;
4.5.23 give the impression that the Contribution emanates from us, if this is not the case; or
4.5.24 disclose any third party’s confidential information, identity, personally identifiable information or personal data (including data concerning health).
-
4.6 You acknowledge and accept that, when using the Platform and accessing the Content, some Content that has been uploaded by third parties may be factually inaccurate, or the topics addressed by the Content may be offensive, indecent, or objectionable in nature. We are not responsible (legally or otherwise) for any claim you may have in relation to the Content.
4.6 Posts must be made with consideration of the NHS People Promise[https://www.england.nhs.uk/our-nhs-people/online-version/lfaop/our-nhs-people-promise/].
4.7 When producing Content to upload to the Platform, we encourage you to implement NICE guideline recommendations and ensure that sources of evidence are valid (for example, by peer review).
5 Metadata
When making any Contribution, you must where prompted include a sufficient description of the Content so that other users can understand the description, source, and age of the Content. For example, if Content has been quality assured, then the relevant information should be posted in the appropriate field. All metadata fields on the Platform must be completed appropriately before initiating upload. Including the correct information is important in order to help other users locate the Content (otherwise the Content may not appear in search results for others to select).
4.5.23 give the impression that the Contribution emanates from us, if this is not the case; or
4.5.24 disclose any third party’s confidential information, identity, personally identifiable information or personal data (including data concerning health).
4.6 Posts must be made with consideration of the NHS People Promise[https://www.england.nhs.uk/our-nhs-people/online-version/lfaop/our-nhs-people-promise/].
-
4.7 When producing Content to upload to the Platform, we encourage you to implement NICE guideline recommendations and ensure that sources of evidence are valid (for example, by peer review).
+
4.7 You acknowledge and accept that, when using the Platform and accessing the Content, some Content that has been uploaded by third parties may be factually inaccurate, or the topics addressed by the Content may be offensive,indecent, or objectionable in nature. We are not responsible (legally or otherwise) for any claim you may have in relation to the Content.
+
4.8 When producing Content to upload to the Platform, we encourage you to implement NICE guideline recommendations and ensure that sources of evidence are valid (for example, by peer review).
5 Metadata
When making any Contribution, you must where prompted include a sufficient description of the Content so that other users can understand the description, source, and age of the Content. For example, if Content has been quality assured, then the relevant information should be posted in the appropriate field. All metadata fields on the Platform must be completed appropriately before initiating upload. Including the correct information is important in order to help other users locate the Content (otherwise the Content may not appear in search results for others to select).
+
When producing Content to upload to the Platform, we encourage you to implement NICE guideline recommendations and ensure that sources evidence are valid (for example, by peer review).
6 Updates
You must update each Contribution at least once every 3 (three) years, or update or remove it should it cease to be relevant or become outdated or revealed or generally perceived to be unsafe or otherwise unsuitable for inclusion on the Platform.
7 Accessibility
@@ -122,13 +124,13 @@
10.4 legal proceedings against you for reimbursement of all costs on an indemnity basis (including, but not limited to, reasonable administrative and legal costs) resulting from the breach, and/or further legal action against you;
10.5 disclosure of such information to law enforcement authorities as we reasonably feel is necessary or as required by law; and/or
10.6 any other action we reasonably deem appropriate.
-
Moderation of Contributions
+
11 Moderation of Contributions
11.1 Course Manager means a person authorised by NHS England who is responsible for creating and managing courses and learning resources on the Learning Hub, including the moderation of learner's Contributions within social learning environments of an online course, such as discussion forums.
11.2 Course Managers shall be responsible for monitoring local forum activity and ensuring compliance of all Contributions with the Learning Hub’s Acceptable Use Policy. Inappropriate Contributions shall be addressed promptly and escalated where necessary to the Learning Hub, who will review the user’s access to the Platform and the relevant forum.
11.3 Course Managers shall also take responsibility for the following:
-
11.3.1 Clear Expectations: Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
-
11.3.2 Inclusive Practice: Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
-
11.3.3 Data Protection and Safety: Ensure that no sensitive or personal data i shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
+
11.3.1
Clear Expectations:
Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
+
11.3.2
Inclusive Practice:
Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
+
11.3.3
Data Protection and Safety:
Ensure that no sensitive or personal data is shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
11.4 All users must comply with this Acceptable Use Policy. Breaches may result in removal of Contributions and/or withdrawal or suspension of user access without notice.
11.1 Course Manager means a person authorised by NHS England who is responsible for creating and managing courses and learning resources on the Learning Hub, including the moderation of learner's Contributions within social learning environments of an online course, such as discussion forums.
11.2 Course Managers shall be responsible for monitoring local forum activity and ensuring compliance of all Contributions with the Learning Hub’s Acceptable Use Policy. Inappropriate Contributions shall be addressed promptly and escalated where necessary to the Learning Hub, who will review the user’s access to the Platform and the relevant forum.
11.3 Course Managers shall also take responsibility for the following:
-
11.3.1
Clear Expectations:
Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
-
11.3.2
Inclusive Practice:
Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
-
11.3.3
Data Protection and Safety:
Ensure that no sensitive or personal data is shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
+
11.3.1 Clear Expectations:Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
+
11.3.2 Inclusive Practice: Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
+
11.3.3 Data Protection and Safety:Ensure that no sensitive or personal data is shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
11.4 All users must comply with this Acceptable Use Policy. Breaches may result in removal of Contributions and/or withdrawal or suspension of user access without notice.
11.1 Course Manager means a person authorised by NHS England who is responsible for creating and managing courses and learning resources on the Learning Hub, including the moderation of learner's Contributions within social learning environments of an online course, such as discussion forums.
11.2 Course Managers shall be responsible for monitoring local forum activity and ensuring compliance of all Contributions with the Learning Hub’s Acceptable Use Policy. Inappropriate Contributions shall be addressed promptly and escalated where necessary to the Learning Hub, who will review the user’s access to the Platform and the relevant forum.
11.3 Course Managers shall also take responsibility for the following:
-
11.3.1 Clear Expectations:Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
-
11.3.2 Inclusive Practice: Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
-
11.3.3 Data Protection and Safety:Ensure that no sensitive or personal data is shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
+
11.3.1Clear Expectations:Set clear local forum rules within their course at the outset, compliantly with this Acceptable Use Policy, which includes expected behaviour, response times and moderation practices.
+
11.3.2 Inclusive Practice: Encourage participation from all learners/users and foster a respectful, inclusive environment that values diverse perspectives. This should involve reminding local forum users of the appropriate measures of this Acceptable Use Policy and potentially removing Contributions that infringe the rules of this Acceptable Use Policy including under paragraph 4.5 above.
+
11.3.3 Data Protection and Safety:Ensure that no sensitive or personal data is shared, requested or stored in local forums, including by promptly removing any inclusions or requests for such material and notifying NHS England promptly of any potential breaches by contacting NHS England’s Data Protection Officer team via england.dpo@nhs.net; and ensure that risks are actively managed and the local forums are maintained as a safe space for discussion.
11.4 All users must comply with this Acceptable Use Policy. Breaches may result in removal of Contributions and/or withdrawal or suspension of user access without notice.