Skip to content

Commit 020de78

Browse files
MIB Integration feature changes - TD-6547 # TD-6548 # TD-6549
1 parent 7a49236 commit 020de78

99 files changed

Lines changed: 1902 additions & 440 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace LearningHub.Nhs.AdminUI.Controllers
22
{
3+
using LearningHub.Nhs.Models.Extensions;
34
using Microsoft.AspNetCore.Authorization;
45
using Microsoft.AspNetCore.Hosting;
56
using Microsoft.AspNetCore.Mvc;
@@ -51,6 +52,11 @@ protected string WebRootPath
5152
}
5253
}
5354

55+
/// <summary>
56+
/// Gets the CurrentUserEmail.
57+
/// </summary>
58+
protected string CurrentUserEmail => this.User.Identity.GetCurrentEmail();
59+
5460
/// <summary>
5561
/// The OnActionExecuting.
5662
/// </summary>

AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs

Lines changed: 99 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
namespace LearningHub.Nhs.AdminUI.Controllers
1+
using LearningHub.Nhs.Models.Databricks;
2+
using Microsoft.AspNetCore.Mvc.Rendering;
3+
using System.Net;
4+
5+
namespace LearningHub.Nhs.AdminUI.Controllers
26
{
7+
using AngleSharp.Io;
8+
using Azure;
39
using LearningHub.Nhs.AdminUI.Configuration;
410
using LearningHub.Nhs.AdminUI.Extensions;
11+
using LearningHub.Nhs.AdminUI.Helpers;
512
using LearningHub.Nhs.AdminUI.Interfaces;
613
using LearningHub.Nhs.AdminUI.Models;
714
using LearningHub.Nhs.Models.Catalogue;
815
using LearningHub.Nhs.Models.Common;
16+
using LearningHub.Nhs.Models.Databricks;
917
using LearningHub.Nhs.Models.Moodle;
1018
using LearningHub.Nhs.Models.Paging;
1119
using LearningHub.Nhs.Models.Resource;
@@ -70,6 +78,11 @@ public class CatalogueController : BaseController
7078
/// </summary>
7179
private readonly IMoodleApiService moodleApiService;
7280

81+
/// <summary>
82+
/// Defines the moodleApiService.
83+
/// </summary>
84+
private readonly IMoodleBridgeApiService moodleBridgeApiService;
85+
7386
/// <summary>
7487
/// Defines the _settings.
7588
/// </summary>
@@ -104,6 +117,7 @@ public class CatalogueController : BaseController
104117
/// <param name="fileService">The fileService<see cref="IFileService"/>.</param>
105118
/// <param name="providerService">The providerService<see cref="IProviderService"/>.</param>
106119
/// <param name="moodleApiService">The moodleApiService<see cref="IMoodleApiService"/>.</param>
120+
/// <param name="moodleBridgeApiService">The moodleApiService<see cref="moodleBridgeApiService"/>.</param>
107121
/// <param name="logger">The logger<see cref="ILogger{LogController}"/>.</param>
108122
/// <param name="options">The options<see cref="IOptions{WebSettings}"/>.</param>
109123
/// <param name="websettings">The websettings<see cref="IOptions{WebSettings}"/>.</param>
@@ -114,6 +128,7 @@ public CatalogueController(
114128
IFileService fileService,
115129
IProviderService providerService,
116130
IMoodleApiService moodleApiService,
131+
IMoodleBridgeApiService moodleBridgeApiService,
117132
ILogger<LogController> logger,
118133
IOptions<WebSettings> options,
119134
IOptions<WebSettings> websettings)
@@ -124,6 +139,7 @@ public CatalogueController(
124139
this.fileService = fileService;
125140
this.providerService = providerService;
126141
this.moodleApiService = moodleApiService;
142+
this.moodleBridgeApiService = moodleBridgeApiService;
127143
this.logger = logger;
128144
this.websettings = websettings;
129145
this.settings = options.Value;
@@ -277,17 +293,9 @@ public async Task<IActionResult> MoodleCategory(int id)
277293
return this.RedirectToAction("Error");
278294
}
279295

280-
var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();
296+
var categoriesResult = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
281297

282-
vm.MoodleCategories = categories;
283-
284-
// Build hierarchical select list
285-
var selectList = BuildList(categories, parentId: null, depth: 0);
286-
foreach (var item in selectList)
287-
{
288-
item.Text = WebUtility.HtmlDecode(item.Text);
289-
}
290-
vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
298+
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categoriesResult);
291299
this.ViewData["CatalogueName"] = vm.Name;
292300
this.ViewData["id"] = id;
293301

@@ -727,7 +735,7 @@ public async Task<IActionResult> AddUserGroupsToCatalogue(int catalogueNodeId, i
727735
[Route("AddCategoryToCatalogue")]
728736
public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catalogueViewModel)
729737
{
730-
if (catalogueViewModel.SelectedCategoryId == 0)
738+
if (string.IsNullOrEmpty(catalogueViewModel.SelectedCategoryId))
731739
{
732740
this.ModelState.AddModelError("SelectedCategoryId", "Please select a category.");
733741
}
@@ -736,17 +744,8 @@ public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catal
736744
var vr = await this.catalogueService.AddCategoryToCatalogue(vm);
737745
if (vr.Success)
738746
{
739-
var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();
740-
vm.MoodleCategories = categories;
741-
// Build hierarchical select list
742-
var selectList = BuildList(categories, parentId: null, depth: 0);
743-
744-
foreach (var item in selectList)
745-
{
746-
item.Text = WebUtility.HtmlDecode(item.Text);
747-
}
748-
749-
vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
747+
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
748+
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);
750749
return this.View("MoodleCategory", vm);
751750
}
752751
else
@@ -764,23 +763,16 @@ public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catal
764763
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
765764
[HttpGet]
766765
[Route("RemoveCategoryFromCatalogue/{categoryId}/{catalogueNodeVersionId}")]
767-
public async Task<IActionResult> RemoveCategoryFromCatalogue(int categoryId, int catalogueNodeVersionId)
766+
public async Task<IActionResult> RemoveCategoryFromCatalogue(string categoryId, int catalogueNodeVersionId)
768767
{
769768
var vm = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId);
770769
vm.SelectedCategoryId = categoryId;
771770
var vr = await this.catalogueService.RemoveCategoryFromCatalogue(vm);
772771
if (vr.Success)
773772
{
774-
var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync();
775-
vm.MoodleCategories = categories;
776-
vm.SelectedCategoryId = 0;
777-
// Build hierarchical select list
778-
var selectList = BuildList(categories, parentId: null, depth: 0);
779-
foreach (var item in selectList)
780-
{
781-
item.Text = WebUtility.HtmlDecode(item.Text);
782-
}
783-
vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
773+
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
774+
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);
775+
vm.SelectedCategoryId = null;
784776
return this.View("MoodleCategory", vm);
785777
}
786778
else
@@ -1090,33 +1082,91 @@ private void ValidateCatalogueOwnerVm(CatalogueOwnerViewModel vm)
10901082
}
10911083
}
10921084

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

1097-
// Handle both null and 0 as top-level depending on Moodle data
1098-
var children = allCategories
1099-
.Where(c => c.Parent == parentId || (parentId == null && (c.Parent == 0 || c.Parent == 0)))
1100-
.OrderBy(c => c.Name)
1101-
.ToList();
1102-
1103-
foreach (var child in children)
1089+
foreach (var result in results)
11041090
{
1105-
// Indent with non-breaking spaces so browser keeps them
1106-
string indent = new string('\u00A0', depth * 3);
1091+
if (string.IsNullOrWhiteSpace(result.Instance))
1092+
continue;
11071093

1094+
// Instance header
11081095
selectList.Add(new SelectListItem
11091096
{
1110-
Value = child.Id.ToString(),
1111-
Text = $"{indent}{child.Name}"
1097+
Value = "",
1098+
Text = result.Instance,
1099+
Disabled = true,
1100+
Selected = false
11121101
});
11131102

1114-
// Recursively add nested children
1115-
selectList.AddRange(BuildList(allCategories, child.Id, depth + 1));
1103+
// Handle instance error
1104+
if (result.Error != null)
1105+
{
1106+
selectList.Add(new SelectListItem
1107+
{
1108+
Value = "",
1109+
Text = "\u00A0\u00A0[Category unavailable]",
1110+
Disabled = true,
1111+
Selected = false
1112+
});
1113+
1114+
continue;
1115+
}
1116+
1117+
var categories = result.Data?.Categories;
1118+
if (categories == null || !categories.Any())
1119+
continue;
1120+
1121+
var tree = categories
1122+
.GroupBy(c => c.Parent)
1123+
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.Name).ToList());
1124+
1125+
TreeBuilderHelper.BuildTree(selectList, tree, 0, 1, result.Instance);
1126+
}
1127+
1128+
// Decode HTML entities
1129+
foreach (var item in selectList)
1130+
{
1131+
item.Text = WebUtility.HtmlDecode(item.Text);
11161132
}
11171133

1118-
return selectList;
1134+
return new SelectList(selectList, "Value", "Text");
11191135
}
11201136

1137+
/// </summary>
1138+
/// <param name="selectList"></param>
1139+
/// <param name="tree"></param>
1140+
/// <param name="parentId"></param>
1141+
/// <param name="depth"></param>
1142+
/// <param name="instance"></param>
1143+
/// <param name="group"></param>
1144+
1145+
// Recursive tree builder with indentation
1146+
private static void BuildTree(
1147+
List<SelectListItem> selectList,
1148+
Dictionary<int, List<MoodleCategory>> tree,
1149+
int parentId,
1150+
int depth,
1151+
string instance,
1152+
SelectListGroup group)
1153+
{
1154+
if (!tree.ContainsKey(parentId))
1155+
return;
1156+
1157+
foreach (var category in tree[parentId])
1158+
{
1159+
string indent = new string('\u00A0', depth * 2);
1160+
1161+
selectList.Add(new SelectListItem
1162+
{
1163+
Value = $"{instance}:{category.Id}",
1164+
Text = $"{indent}{category.Name}",
1165+
Group = group
1166+
});
1167+
1168+
BuildTree(selectList, tree, category.Id, depth + 1, instance, group);
1169+
}
1170+
}
11211171
}
11221172
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace LearningHub.Nhs.AdminUI.Helpers
2+
{
3+
using LearningHub.Nhs.Models.Moodle;
4+
using Microsoft.AspNetCore.Mvc.Rendering;
5+
using System.Collections.Generic;
6+
7+
/// <summary>
8+
/// Defines the <see cref="TreeBuilderHelper" />.
9+
/// </summary>
10+
public static class TreeBuilderHelper
11+
{
12+
/// <summary>
13+
/// BuildSelectListFromCategories.
14+
/// </summary>
15+
/// <param name="selectList">The selectList.</param>
16+
/// <param name="tree">The tree.</param>
17+
/// <param name="parentId">The parentId.</param>
18+
/// <param name="depth">The depth.</param>
19+
/// <param name="instance">The instance.</param>
20+
public static void BuildTree(
21+
List<SelectListItem> selectList,
22+
Dictionary<int, List<MoodleCategory>> tree,
23+
int parentId,
24+
int depth,
25+
string instance)
26+
{
27+
if (!tree.ContainsKey(parentId))
28+
return;
29+
30+
foreach (var category in tree[parentId])
31+
{
32+
string indent = new string('\u00A0', depth * 2);
33+
34+
selectList.Add(new SelectListItem
35+
{
36+
Value = $"{instance}:{category.Id}",
37+
Text = $"{indent}{category.Name}"
38+
});
39+
40+
BuildTree(selectList, tree, category.Id, depth + 1, instance);
41+
}
42+
}
43+
}
44+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace LearningHub.Nhs.AdminUI.Interfaces
2+
{
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
using LearningHub.Nhs.Models.Moodle;
6+
using LearningHub.Nhs.Models.Moodle.API;
7+
8+
/// <summary>
9+
/// IMoodleBridgeApiService.
10+
/// </summary>
11+
public interface IMoodleBridgeApiService
12+
{
13+
/// <summary>
14+
/// GetAllMoodleCategoriesAsync.
15+
/// </summary>
16+
/// <returns> List of MoodleCategory.</returns>
17+
Task<List<CategoryResult>> GetAllMoodleCategoriesAsync();
18+
}
19+
}

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
<PackageReference Include="HtmlSanitizer" Version="6.0.453" />
9090
<PackageReference Include="IdentityModel" Version="4.6.0" />
9191
<PackageReference Include="LearningHub.Nhs.Caching" Version="2.0.2" />
92-
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.12" />
92+
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.13" />
9393
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.19.0" />
9494
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.36" />
9595
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.36" />

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
55
</PropertyGroup>
66
<PropertyGroup>
7-
<ActiveDebugProfile>Local IIS</ActiveDebugProfile>
7+
<ActiveDebugProfile>IIS Local</ActiveDebugProfile>
88
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
99
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
1010
<View_SelectedScaffolderID>RazorViewEmptyScaffolder</View_SelectedScaffolderID>

AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
107107
services.AddTransient<IInternalSystemService, InternalSystemService>();
108108
services.AddScoped<IProviderService, ProviderService>();
109109
services.AddScoped<IMoodleApiService, MoodleApiService>();
110+
services.AddScoped<IMoodleBridgeApiService, MoodleBridgeApiService>();
110111

111112
// Configure Azure Search
112113
services.Configure<AzureSearchConfig>(configuration.GetSection("AzureSearch"));

0 commit comments

Comments
 (0)