Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
148 changes: 99 additions & 49 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();
var categoriesResult = await this.moodleBridgeApiService.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);
}
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,7 +735,7 @@ 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.");
}
Expand All @@ -736,17 +744,8 @@ public async Task<IActionResult> AddCategoryToCatalogue(CatalogueViewModel catal
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);
}

vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text");
var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync();
vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories);
return this.View("MoodleCategory", vm);
}
else
Expand All @@ -764,23 +763,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 +1082,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.13" />
<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