Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
020de78
MIB Integration feature changes - TD-6547 # TD-6548 # TD-6549
swapnamol-abraham Mar 19, 2026
1323728
Merge branch 'RC' into Develop/Feature/MIB_Integration
swapnamol-abraham Mar 19, 2026
cce3e9d
Removed commented lines and unused files
swapnamol-abraham Mar 19, 2026
210a43b
Merge branch 'Develop/Feature/MIB_Integration' of https://github.com/…
swapnamol-abraham Mar 19, 2026
38f435d
Merge pull request #1745 from TechnologyEnhancedLearning/Develop/Feat…
AnjuJose011 Mar 23, 2026
7c8e67e
Added course url as part of moodle course object
swapnamol-abraham Mar 26, 2026
c15abe7
Merge pull request #1750 from TechnologyEnhancedLearning/Develop/Feat…
AnjuJose011 Mar 27, 2026
b2a9c77
TD-7051: LH prod db performance improvement and foreign key dependenc…
Mar 27, 2026
a942424
Merge pull request #1754 from TechnologyEnhancedLearning/Develop/Feat…
sarathlal-sarangadharan Mar 30, 2026
e0feee2
Merge branch 'RC' of https://github.com/TechnologyEnhancedLearning/Le…
swapnamol-abraham Apr 7, 2026
b493b8e
TD-7069: Call the MIB endpoint from wherever LH business logic exists…
swapnamol-abraham Apr 7, 2026
e4f5faf
Merge pull request #1763 from TechnologyEnhancedLearning/Develop/TD-7…
AnjuJose011 Apr 7, 2026
43923e7
Merge pull request #1767 from TechnologyEnhancedLearning/Develop/Feat…
AnjuJose011 Apr 7, 2026
e9cd8af
Merge branch 'RC' of https://github.com/TechnologyEnhancedLearning/Le…
swapnamol-abraham Apr 8, 2026
dc358b4
Merge pull request #1768 from TechnologyEnhancedLearning/RC-OLD
AnjuJose011 Apr 8, 2026
9f82cc3
Merge branch 'RC' of https://github.com/TechnologyEnhancedLearning/Le…
swapnamol-abraham Apr 9, 2026
d9120d9
TD-7108: My courses and elearning Tray - eLearning and the Recently C…
swapnamol-abraham Apr 9, 2026
bccb37c
Correc ted for recently completed tray
swapnamol-abraham Apr 9, 2026
cbc39fe
Merge pull request #1769 from TechnologyEnhancedLearning/Develop/Fixe…
swapnamol-abraham Apr 9, 2026
a28dfac
handled empty records
swapnamol-abraham Apr 9, 2026
61f163a
Merge pull request #1771 from TechnologyEnhancedLearning/Develop/Fixe…
AnjuJose011 Apr 9, 2026
c43b6a6
TD-7116, script to create new moodle instance config table and and sc…
Apr 10, 2026
9ce2337
Made it consistent with other script inserts
Apr 10, 2026
e5d73c4
Merge pull request #1772 from TechnologyEnhancedLearning/Develop/Feat…
AnjuJose011 Apr 10, 2026
d688d7d
Resume Change Tracking after each deployment.
OluwatobiAwe Apr 13, 2026
8c6ff38
.
OluwatobiAwe Apr 13, 2026
34a74ec
Merge pull request #1775 from TechnologyEnhancedLearning/Develop/Fixe…
OluwatobiAwe Apr 14, 2026
c0c372a
removed the findwise references in WebAPI
Apr 14, 2026
c55e1a1
removed the remaining findwise references in webapi
Apr 14, 2026
1c8793f
Merge pull request #1777 from TechnologyEnhancedLearning/Develop/Feat…
binon Apr 15, 2026
d3f7710
TD-7125: LH Admin UI - when category is not assigned to the catalogue…
swapnamol-abraham Apr 16, 2026
1761847
Need to remosve some more dependency in openAPI project.
Apr 16, 2026
3bf5653
Merge pull request #1778 from TechnologyEnhancedLearning/Develop/Fixe…
swapnamol-abraham Apr 16, 2026
d81444f
Merge pull request #1779 from TechnologyEnhancedLearning/Develop/Feat…
binon Apr 16, 2026
ef29bbe
TD-7126: My courses and learning - elearning resources doesn't appea…
swapnamol-abraham Apr 16, 2026
5a3148a
Removed unwanted lines
swapnamol-abraham Apr 16, 2026
7b6f5e4
Merge pull request #1780 from TechnologyEnhancedLearning/Develop/Fixe…
AnjuJose011 Apr 16, 2026
304425c
fixed the issue which was always returning false
Apr 16, 2026
dc6a833
Remove more dependencies
Apr 16, 2026
962c9c1
Merge pull request #1781 from TechnologyEnhancedLearning/Develop/Feat…
binon Apr 16, 2026
d2a5740
activate CT if off
OluwatobiAwe Apr 17, 2026
27230c7
TD-7126: Issue with the recent learning
swapnamol-abraham Apr 17, 2026
0dfce41
Merge pull request #1782 from TechnologyEnhancedLearning/Develop/Fixe…
AnjuJose011 Apr 17, 2026
7c0ab36
CT reactivation on tables
OluwatobiAwe Apr 17, 2026
3525ba4
Merge pull request #1783 from TechnologyEnhancedLearning/Change_Track…
OluwatobiAwe Apr 17, 2026
011bbcb
CT retention days update
OluwatobiAwe Apr 17, 2026
ce7381d
Merge pull request #1784 from TechnologyEnhancedLearning/Change_Track…
OluwatobiAwe Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace LearningHub.Nhs.AdminUI.Controllers
{
using LearningHub.Nhs.Models.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -51,6 +52,11 @@ protected string WebRootPath
}
}

/// <summary>
/// Gets the CurrentUserEmail.
/// </summary>
protected string CurrentUserEmail => this.User.Identity.GetCurrentEmail();

/// <summary>
/// The OnActionExecuting.
/// </summary>
Expand Down
172 changes: 116 additions & 56 deletions AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
namespace LearningHub.Nhs.AdminUI.Controllers
using LearningHub.Nhs.Models.Databricks;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Net;

namespace LearningHub.Nhs.AdminUI.Controllers
{
using AngleSharp.Io;
using Azure;
using LearningHub.Nhs.AdminUI.Configuration;
using LearningHub.Nhs.AdminUI.Extensions;
using LearningHub.Nhs.AdminUI.Helpers;
using LearningHub.Nhs.AdminUI.Interfaces;
using LearningHub.Nhs.AdminUI.Models;
using LearningHub.Nhs.Models.Catalogue;
using LearningHub.Nhs.Models.Common;
using LearningHub.Nhs.Models.Databricks;
using LearningHub.Nhs.Models.Moodle;
using LearningHub.Nhs.Models.Paging;
using LearningHub.Nhs.Models.Resource;
Expand Down Expand Up @@ -70,6 +78,11 @@ public class CatalogueController : BaseController
/// </summary>
private readonly IMoodleApiService moodleApiService;

/// <summary>
/// Defines the moodleApiService.
/// </summary>
private readonly IMoodleBridgeApiService moodleBridgeApiService;

/// <summary>
/// Defines the _settings.
/// </summary>
Expand Down Expand Up @@ -104,6 +117,7 @@ public class CatalogueController : BaseController
/// <param name="fileService">The fileService<see cref="IFileService"/>.</param>
/// <param name="providerService">The providerService<see cref="IProviderService"/>.</param>
/// <param name="moodleApiService">The moodleApiService<see cref="IMoodleApiService"/>.</param>
/// <param name="moodleBridgeApiService">The moodleApiService<see cref="moodleBridgeApiService"/>.</param>
/// <param name="logger">The logger<see cref="ILogger{LogController}"/>.</param>
/// <param name="options">The options<see cref="IOptions{WebSettings}"/>.</param>
/// <param name="websettings">The websettings<see cref="IOptions{WebSettings}"/>.</param>
Expand All @@ -114,6 +128,7 @@ public CatalogueController(
IFileService fileService,
IProviderService providerService,
IMoodleApiService moodleApiService,
IMoodleBridgeApiService moodleBridgeApiService,
ILogger<LogController> logger,
IOptions<WebSettings> options,
IOptions<WebSettings> websettings)
Expand All @@ -124,6 +139,7 @@ public CatalogueController(
this.fileService = fileService;
this.providerService = providerService;
this.moodleApiService = moodleApiService;
this.moodleBridgeApiService = moodleBridgeApiService;
this.logger = logger;
this.websettings = websettings;
this.settings = options.Value;
Expand Down Expand Up @@ -277,17 +293,9 @@ public async Task<IActionResult> MoodleCategory(int id)
return this.RedirectToAction("Error");
}

var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();

vm.MoodleCategories = categories;
var categoriesResult = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();

// Build hierarchical select list
var selectList = BuildList(categories, parentId: null, depth: 0);
foreach (var item in selectList)
{
item.Text = WebUtility.HtmlDecode(item.Text);
}
vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categoriesResult);
this.ViewData["CatalogueName"] = vm.Name;
this.ViewData["id"] = id;

Expand Down Expand Up @@ -727,32 +735,33 @@ public async Task<IActionResult> AddUserGroupsToCatalogue(int catalogueNodeId, i
[Route("AddCategoryToCatalogue")]
public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catalogueViewModel)
{
if (catalogueViewModel.SelectedCategoryId == 0)
if (string.IsNullOrEmpty(catalogueViewModel.SelectedCategoryId))
{
this.ModelState.AddModelError("SelectedCategoryId", "Please select a category.");
}
var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId);
vm.SelectedCategoryId = catalogueViewModel.SelectedCategoryId;
var vr = await this.catalogueService.AddCategoryToCatalogue(vm);
if (vr.Success)
{
var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();
vm.MoodleCategories = categories;
// Build hierarchical select list
var selectList = BuildList(categories, parentId: null, depth: 0);

foreach (var item in selectList)
{
item.Text = WebUtility.HtmlDecode(item.Text);
}
var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId);
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
vm.SelectedCategoryId = null;
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);

vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
return this.View("MoodleCategory", vm);
}

else
{
this.ViewBag.ErrorMessage = $"Category Update failed.";
return this.View("MoodleCategory", vm);
var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId);
vm.SelectedCategoryId = catalogueViewModel.SelectedCategoryId;
var vr = await this.catalogueService.AddCategoryToCatalogue(vm);
if (vr.Success)
{
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);
return this.View("MoodleCategory", vm);
}
else
{
this.ViewBag.ErrorMessage = $"Category Update failed.";
return this.View("MoodleCategory", vm);
}
}
}

Expand All @@ -764,23 +773,16 @@ public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catal
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
[HttpGet]
[Route("RemoveCategoryFromCatalogue/{categoryId}/{catalogueNodeVersionId}")]
public async Task<IActionResult> RemoveCategoryFromCatalogue(int categoryId, int catalogueNodeVersionId)
public async Task<IActionResult> RemoveCategoryFromCatalogue(string categoryId, int catalogueNodeVersionId)
{
var vm = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId);
vm.SelectedCategoryId = categoryId;
var vr = await this.catalogueService.RemoveCategoryFromCatalogue(vm);
if (vr.Success)
{
var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();
vm.MoodleCategories = categories;
vm.SelectedCategoryId = 0;
// Build hierarchical select list
var selectList = BuildList(categories, parentId: null, depth: 0);
foreach (var item in selectList)
{
item.Text = WebUtility.HtmlDecode(item.Text);
}
vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);
vm.SelectedCategoryId = null;
return this.View("MoodleCategory", vm);
}
else
Expand Down Expand Up @@ -1090,33 +1092,91 @@ private void ValidateCatalogueOwnerVm(CatalogueOwnerViewModel vm)
}
}

private List<SelectListItem> BuildList(IEnumerable<MoodleCategory> allCategories, int? parentId, int depth)
public static SelectList BuildMoodleCategorySelectList(IEnumerable<CategoryResult> results)
{
var selectList = new List<SelectListItem>();

// Handle both null and 0 as top-level depending on Moodle data
var children = allCategories
.Where(c => c.Parent == parentId || (parentId == null && (c.Parent == 0 || c.Parent == 0)))
.OrderBy(c => c.Name)
.ToList();

foreach (var child in children)
foreach (var result in results)
{
// Indent with non-breaking spaces so browser keeps them
string indent = new string('\u00A0', depth * 3);
if (string.IsNullOrWhiteSpace(result.Instance))
continue;

// Instance header
selectList.Add(new SelectListItem
{
Value = child.Id.ToString(),
Text = $"{indent}{child.Name}"
Value = "",
Text = result.Instance,
Disabled = true,
Selected = false
});

// Recursively add nested children
selectList.AddRange(BuildList(allCategories, child.Id, depth + 1));
// Handle instance error
if (result.Error != null)
{
selectList.Add(new SelectListItem
{
Value = "",
Text = "\u00A0\u00A0[Category unavailable]",
Disabled = true,
Selected = false
});

continue;
}

var categories = result.Data?.Categories;
if (categories == null || !categories.Any())
continue;

var tree = categories
.GroupBy(c => c.Parent)
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.Name).ToList());

TreeBuilderHelper.BuildTree(selectList, tree, 0, 1, result.Instance);
}

// Decode HTML entities
foreach (var item in selectList)
{
item.Text = WebUtility.HtmlDecode(item.Text);
}

return selectList;
return new SelectList(selectList, "Value", "Text");
}

/// </summary>
/// <param name="selectList"></param>
/// <param name="tree"></param>
/// <param name="parentId"></param>
/// <param name="depth"></param>
/// <param name="instance"></param>
/// <param name="group"></param>

// Recursive tree builder with indentation
private static void BuildTree(
List<SelectListItem> selectList,
Dictionary<int, List<MoodleCategory>> tree,
int parentId,
int depth,
string instance,
SelectListGroup group)
{
if (!tree.ContainsKey(parentId))
return;

foreach (var category in tree[parentId])
{
string indent = new string('\u00A0', depth * 2);

selectList.Add(new SelectListItem
{
Value = $"{instance}:{category.Id}",
Text = $"{indent}{category.Name}",
Group = group
});

BuildTree(selectList, tree, category.Id, depth + 1, instance, group);
}
}
}
}
44 changes: 44 additions & 0 deletions AdminUI/LearningHub.Nhs.AdminUI/Helpers/TreeBuilderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace LearningHub.Nhs.AdminUI.Helpers
{
using LearningHub.Nhs.Models.Moodle;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

/// <summary>
/// Defines the <see cref="TreeBuilderHelper" />.
/// </summary>
public static class TreeBuilderHelper
{
/// <summary>
/// BuildSelectListFromCategories.
/// </summary>
/// <param name="selectList">The selectList.</param>
/// <param name="tree">The tree.</param>
/// <param name="parentId">The parentId.</param>
/// <param name="depth">The depth.</param>
/// <param name="instance">The instance.</param>
public static void BuildTree(
List<SelectListItem> selectList,
Dictionary<int, List<MoodleCategory>> tree,
int parentId,
int depth,
string instance)
{
if (!tree.ContainsKey(parentId))
return;

foreach (var category in tree[parentId])
{
string indent = new string('\u00A0', depth * 2);

selectList.Add(new SelectListItem
{
Value = $"{instance}:{category.Id}",
Text = $"{indent}{category.Name}"
});

BuildTree(selectList, tree, category.Id, depth + 1, instance);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace LearningHub.Nhs.AdminUI.Interfaces
{
using System.Collections.Generic;
using System.Threading.Tasks;
using LearningHub.Nhs.Models.Moodle;
using LearningHub.Nhs.Models.Moodle.API;

/// <summary>
/// IMoodleBridgeApiService.
/// </summary>
public interface IMoodleBridgeApiService
{
/// <summary>
/// GetAllMoodleCategoriesAsync.
/// </summary>
/// <returns> List of MoodleCategory.</returns>
Task<List<CategoryResult>> GetAllMoodleCategoriesAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<PackageReference Include="HtmlSanitizer" Version="6.0.453" />
<PackageReference Include="IdentityModel" Version="4.6.0" />
<PackageReference Include="LearningHub.Nhs.Caching" Version="2.0.2" />
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.12" />
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.16" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.19.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.36" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.36" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>Local IIS</ActiveDebugProfile>
<ActiveDebugProfile>IIS Local</ActiveDebugProfile>
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
<View_SelectedScaffolderID>RazorViewEmptyScaffolder</View_SelectedScaffolderID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
services.AddTransient<IInternalSystemService, InternalSystemService>();
services.AddScoped<IProviderService, ProviderService>();
services.AddScoped<IMoodleApiService, MoodleApiService>();
services.AddScoped<IMoodleBridgeApiService, MoodleBridgeApiService>();

// Configure Azure Search
services.Configure<AzureSearchConfig>(configuration.GetSection("AzureSearch"));
Expand Down
Loading
Loading