diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Configuration/MoodleApiConfig.cs b/AdminUI/LearningHub.Nhs.AdminUI/Configuration/MoodleApiConfig.cs new file mode 100644 index 000000000..88b8c8d9e --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Configuration/MoodleApiConfig.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.AdminUI.Configuration +{ + /// + /// The Moodle Settings. + /// + public class MoodleApiConfig + { + /// + /// Gets or sets the base url for the Moodle service. + /// + public string BaseUrl { get; set; } = null!; + + /// + /// Gets or sets the Web service Rest Format. + /// + public string MoodleWSRestFormat { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string WSToken { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string ApiPath { get; set; } = "webservice/rest/server.php"; + + /// + /// Gets or sets the token. + /// + public string CoursePath { get; set; } = "course/view.php"; + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs index d03f1f84c..44dc90275 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs @@ -1,6 +1,7 @@ namespace LearningHub.Nhs.AdminUI.Controllers { using System; + using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Drawing; @@ -8,6 +9,7 @@ using System.Net.Mail; using System.Text.RegularExpressions; using System.Threading.Tasks; + using AngleSharp.Io; using LearningHub.Nhs.AdminUI.Configuration; using LearningHub.Nhs.AdminUI.Extensions; using LearningHub.Nhs.AdminUI.Interfaces; @@ -15,12 +17,16 @@ using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Common.Enums; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -65,6 +71,11 @@ public class CatalogueController : BaseController /// private readonly IProviderService providerService; + /// + /// Defines the moodleApiService. + /// + private readonly IMoodleApiService moodleApiService; + /// /// Defines the _settings. /// @@ -98,6 +109,7 @@ public class CatalogueController : BaseController /// The userService. /// The fileService. /// The providerService. + /// The moodleApiService. /// The logger. /// The options. /// The websettings. @@ -107,6 +119,7 @@ public CatalogueController( IUserGroupService userGroupService, IFileService fileService, IProviderService providerService, + IMoodleApiService moodleApiService, ILogger logger, IOptions options, IOptions websettings) @@ -116,6 +129,7 @@ public CatalogueController( this.userGroupService = userGroupService; this.fileService = fileService; this.providerService = providerService; + this.moodleApiService = moodleApiService; this.logger = logger; this.websettings = websettings; this.settings = options.Value; @@ -254,6 +268,29 @@ public async Task CatalogueOwner(int id) return this.View(vm); } + /// + /// The CatalogueOwner. + /// + /// The id. + /// The . + [HttpGet] + [Route("MoodleCategory/{id}")] + public async Task MoodleCategory(int id) + { + var vm = await this.catalogueService.GetCatalogueAsync(id); + if (vm == null) + { + return this.RedirectToAction("Error"); + } + + vm.MoodleCategories = await this.moodleApiService.GetAllMoodleCategoriesAsync(); + vm.MoodleCategorySelectList = new SelectList(vm.MoodleCategories, "Id", "Name"); + this.ViewData["CatalogueName"] = vm.Name; + this.ViewData["id"] = id; + + return this.View(vm); + } + /// /// The UserGroups. /// @@ -678,6 +715,35 @@ public async Task AddUserGroupsToCatalogue(int catalogueNodeId, i } } + /// + /// The AddCategoryToCatalogue. + /// + /// The catalogueViewModel. + /// The . + [HttpPost] + [Route("AddCategoryToCatalogue")] + public async Task AddCategoryToCatalogue(CatalogueViewModel catalogueViewModel) + { + ////if (catalogueViewModel.SelectedCategoryId == 0) + ////{ + //// 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) + { + vm.MoodleCategories = await this.moodleApiService.GetAllMoodleCategoriesAsync(); + vm.MoodleCategorySelectList = new SelectList(vm.MoodleCategories, "Id", "Name"); + return this.View("MoodleCategory", vm); + } + else + { + this.ViewBag.ErrorMessage = $"Category Update failed."; + return this.View("MoodleCategory", vm); + } + } + /// /// The CreateCatalogue. /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/ICatalogueService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/ICatalogueService.cs index f6bffaec9..5d58744b4 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/ICatalogueService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/ICatalogueService.cs @@ -69,5 +69,12 @@ public interface ICatalogueService /// The catalogue owner. /// The . Task UpdateCatalogueOwnerAsync(CatalogueOwnerViewModel catalogueOwner); + + /// + /// AddCategoryToCatalogue + /// + /// The catalogue. + /// + Task AddCategoryToCatalogue(CatalogueViewModel catalogue); } } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleApiService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleApiService.cs new file mode 100644 index 000000000..d8c384757 --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleApiService.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.AdminUI.Interfaces +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + + /// + /// IMoodleApiService. + /// + public interface IMoodleApiService + { + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// The current LH User Id. + /// A representing the result of the asynchronous operation. + Task GetMoodleUserIdByUsernameAsync(int currentUserId); + + /// + /// GetEnrolledCoursesAsync. + /// + /// List of MoodleCategory. + Task> GetAllMoodleCategoriesAsync(); + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index 126c014ef..39e4b25da 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Models/CatalogueNavViewModel.cs b/AdminUI/LearningHub.Nhs.AdminUI/Models/CatalogueNavViewModel.cs index 502206549..4cf605e72 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Models/CatalogueNavViewModel.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Models/CatalogueNavViewModel.cs @@ -34,6 +34,11 @@ public enum CatalogueNavPage /// Defines the Catalogue Owner. /// CatalogueOwner, + + /// + /// Defines the Category. + /// + Category } /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs index 1039a3673..4f95fe9fb 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs @@ -106,6 +106,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddTransient(); services.AddTransient(); services.AddScoped(); + services.AddScoped(); // web settings binding var webSettings = new WebSettings(); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs index 2ac0088d9..b7823242e 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs @@ -1,14 +1,20 @@ namespace LearningHub.Nhs.AdminUI.Services { + using System; using System.Collections.Generic; using System.Net; + using System.Net.Http; + using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.AdminUI.Helpers; using LearningHub.Nhs.AdminUI.Interfaces; using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Provider; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; using Newtonsoft.Json; /// @@ -132,5 +138,15 @@ public async Task UpdateCatalogueOwnerAsync(CatalogueOwnerViewModel { return await this.facade.PutAsync("Catalogue/UpdateCatalogueOwner", catalogueOwner); } + + /// + /// The AddUserGroupsToCatalogue. + /// + /// The CatalogueViewModel. + /// The . + public async Task AddCategoryToCatalogue(CatalogueViewModel catalogue) + { + return await this.facade.PostAsync("Catalogue/AddCategoryToCatalogue", catalogue); + } } } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleApiService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleApiService.cs new file mode 100644 index 000000000..febd41a5e --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleApiService.cs @@ -0,0 +1,99 @@ +namespace LearningHub.Nhs.AdminUI.Services +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.AdminUI.Configuration; + using LearningHub.Nhs.AdminUI.Interfaces; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + + /// + /// MoodleApiService. + /// + public class MoodleApiService : IMoodleApiService + { + private readonly IOpenApiHttpClient openApiHttpClient; + private readonly MoodleApiConfig configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The Open Api Http Client. + /// configuration. + public MoodleApiService(IOpenApiHttpClient openApiHttpClient, IOptions configuration) + { + this.openApiHttpClient = openApiHttpClient; + this.configuration = configuration.Value; + } + + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// current User Id. + /// UserId from Moodle. + public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) + { + int moodleUserId = 0; + + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var request = $"Moodle/GetMoodleUserId/{currentUserId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + moodleUserId = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return moodleUserId; + } + catch (Exception ex) + { + return moodleUserId; + } + } + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + public async Task> GetAllMoodleCategoriesAsync() + { + List viewmodel = new List(); + + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var request = $"Moodle/GetAllMoodleCategories"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + catch (Exception ex) + { + return viewmodel; + } + } + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Styles/nhsuk/common.scss b/AdminUI/LearningHub.Nhs.AdminUI/Styles/nhsuk/common.scss index 65d34f7e7..e4d98671c 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Styles/nhsuk/common.scss +++ b/AdminUI/LearningHub.Nhs.AdminUI/Styles/nhsuk/common.scss @@ -7,4 +7,7 @@ .menu-font { font-size: 19px !important; -} \ No newline at end of file +} + + + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml new file mode 100644 index 000000000..be63f99e2 --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml @@ -0,0 +1,107 @@ +@model LearningHub.Nhs.Models.Catalogue.CatalogueViewModel +@inject Microsoft.Extensions.Options.IOptions _settings +@using LearningHub.Nhs.Models.Enums +@using Newtonsoft.Json +@{ + var page = CatalogueNavPage.Category; + ViewData["Title"] = "Manage Category"; + var selectedCategoryName = Model.MoodleCategorySelectList + .FirstOrDefault(c => c.Value == Model.SelectedCategoryId.ToString())?.Text; + var errorHasOccurred = !ViewData.ModelState.IsValid; +} +@section SideMenu { + @{ + await Html.RenderPartialAsync("_NavSection"); + } +} + +
+
+

@Model.Name

+
+
+ +@{ + await Html.RenderPartialAsync("_CatalogueNav.cshtml", new CatalogueNavViewModel { Page = page, CatalogueId = Model.CatalogueNodeVersionId }); +} + +
+
+ + + + + +
+
+

Manage category

+
+
+
+
+
+ @if (Model.SelectedCategoryId > 0) + { +
+ + @selectedCategoryName + Change +
+ } + +
+
+ @if (errorHasOccurred) + { + + Error: Please select a category. + + } +
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+ +@section Scripts { + +} \ No newline at end of file diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/_CatalogueNav.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/_CatalogueNav.cshtml index be71c5771..c7ca0ab8a 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/_CatalogueNav.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/_CatalogueNav.cshtml @@ -5,6 +5,7 @@ var foldersActive = (Model.Page == CatalogueNavPage.Folders) ? "active" : ""; var userGroupsActive = (Model.Page == CatalogueNavPage.UserGroups) ? "active" : ""; var catalogueOwnerActive = (Model.Page == CatalogueNavPage.CatalogueOwner) ? "active" : ""; + var categoryActive = (Model.Page == CatalogueNavPage.Category) ? "active" : ""; }
diff --git a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json index 95a04b8bc..17b29013c 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json +++ b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json @@ -59,6 +59,11 @@ "JWTPrimaryKeySecret": "", "MKPlayerLicence": "", "MediaKindStorageConnectionString": "" + }, + "MoodleAPIConfig": { + "BaseUrl": "https://moodle-test.test-learninghub.org.uk/", + "MoodleWSRestFormat": "json", + "WSToken": "" } }, "ConnectionStrings": { diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index a423f7061..e22685732 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -10,10 +10,22 @@ True + + + + + + + PreserveNewest + true + PreserveNewest + + + - + diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 12d9973ac..0825b7a9e 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -11,6 +11,7 @@ using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Search; using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Configuration; @@ -35,6 +36,7 @@ public class CatalogueController : BaseController private readonly IDashboardService dashboardService; private readonly ISearchService searchService; private readonly ICacheService cacheService; + private readonly ICategoryService categoryService; private LearningHubAuthServiceConfig authConfig; private ICatalogueService catalogueService; private IUserService userService; @@ -57,6 +59,7 @@ public class CatalogueController : BaseController /// Dashboard service. /// HierarchyService. /// userGroupService. + /// categoryService. public CatalogueController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -69,7 +72,8 @@ public CatalogueController( ICacheService cacheService, IDashboardService dashboardService, IHierarchyService hierarchyService, - IUserGroupService userGroupService) + IUserGroupService userGroupService, + ICategoryService categoryService) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.authConfig = authConfig; @@ -81,6 +85,7 @@ public CatalogueController( this.dashboardService = dashboardService; this.hierarchyService = hierarchyService; this.userGroupService = userGroupService; + this.categoryService = categoryService; } /// @@ -187,12 +192,13 @@ public IActionResult CatalogueWithAuthentication(string reference) /// The tab name to display. /// The nodeId of the current folder. If not supplied, catalogue root contents are displayed. /// The SearchRequestViewModel. + /// The moodleCategoryId. /// IActionResult. [AllowAnonymous] [ServiceFilter(typeof(SsoLoginFilterAttribute))] [HttpGet] [Route("catalogue/{reference}/{tab?}")] - public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search) + public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search, int? moodleCategoryId) { if (tab == null || (tab == "search" && !this.User.Identity.IsAuthenticated)) { @@ -206,7 +212,8 @@ public async Task IndexAsync(string reference, string tab, int? n this.ViewBag.ActiveTab = tab; var catalogue = await this.catalogueService.GetCatalogueAsync(reference); - + var catalogueCategoryId = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.NodeVersionId); + catalogue.SelectedCategoryId = catalogueCategoryId; if (catalogue == null) { return this.RedirectToAction("Error", "Home"); @@ -295,10 +302,79 @@ public async Task IndexAsync(string reference, string tab, int? n viewModel.SearchResults = searchResult; } } + else if (tab == "courses") + { + int categoryId = moodleCategoryId ?? await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.NodeVersionId); + var response = await this.categoryService.GetCoursesByCategoryIdAsync(categoryId); + viewModel.Courses = response.Courses; + + var subCategories = await this.categoryService.GetSubCategoryByCategoryIdAsync(categoryId); + viewModel.SubCategories = subCategories; + + if (moodleCategoryId.HasValue) + { + var moodleCategories = await this.categoryService.GetAllMoodleCategoriesAsync(); + + // Start with the selected category + var breadcrumbCategory = moodleCategories.FirstOrDefault(x => x.Id == moodleCategoryId); + + List categories = new List(); + categories.Insert(0, new MoodleCategory + { + Id = catalogue.NodeId, + Name = catalogue.Name, + }); + + while (breadcrumbCategory != null) + { + // Add the current category to the breadcrumb list + categories.Insert(1, breadcrumbCategory); + + // If there's no parent, stop + if (breadcrumbCategory.Parent == 0 || breadcrumbCategory.Parent == catalogueCategoryId) + { + break; + } + + // Move up one level + breadcrumbCategory = moodleCategories.FirstOrDefault(x => x.Id == breadcrumbCategory.Parent); + } + + viewModel.MoodleCategories = categories; + } + else + { + // Otherwise user is looking at catalogue root. + nodeId = catalogue.NodeId; + + viewModel.MoodleCategories = new List + { + new MoodleCategory { Name = catalogue.Name }, + }; + } + } return this.View(viewModel); } + /// + /// GetCourses. + /// + /// The CatalogueNodeVerstionId. + /// The reference. + /// The tab. + /// IActionResult. + [AllowAnonymous] + [ServiceFilter(typeof(SsoLoginFilterAttribute))] + [HttpGet] + [Route("GetCourses/{catalogueNodeVerstionId}/{reference}/{tab}")] + public async Task GetCourses(int catalogueNodeVerstionId, string reference, string tab) + { + var categoryId = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogueNodeVerstionId); + var response = await this.categoryService.GetCoursesByCategoryIdAsync(categoryId); + return this.PartialView("Courses", response.Courses); + } + /// /// Handles sort and filter functionality in Search tab. /// Based on SearchController.IndexPost method. diff --git a/LearningHub.Nhs.WebUI/Helpers/HtmFormatlHelper.cs b/LearningHub.Nhs.WebUI/Helpers/HtmFormatlHelper.cs new file mode 100644 index 000000000..04e8aac9d --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/HtmFormatlHelper.cs @@ -0,0 +1,27 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using HtmlAgilityPack; + + /// + /// HtmFormatlHelper. + /// + public static class HtmFormatlHelper + { + /// + /// StripHtmlTags. + /// + /// html. + /// string. + public static string StripHtmlTags(string html) + { + if (string.IsNullOrEmpty(html)) + { + return string.Empty; + } + + var doc = new HtmlDocument(); + doc.LoadHtml(html); + return doc.DocumentNode.InnerText; + } + } +} diff --git a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index 9f60a93dc..762695dd9 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -8,6 +8,7 @@ using HtmlAgilityPack; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Models.Moodle; using Microsoft.AspNetCore.Mvc.Rendering; /// @@ -496,5 +497,30 @@ public static string GetPillColour(string filename) return breadcrumbs; } + + /// + /// Returns breadcrumb tuple data ready to be passed into the _Breadcrumbs partial view when being used to display a folder path. + /// + /// The list of folder nodes to display. + /// The URL reference of the catalogue. + /// A list of tuples, composed of Title and Url. + public static List<(string Title, string Url)> GetBreadcrumbsForCourses(List moodleCategories, string catalogueUrl) + { + // Create breadcrumb tuple data + var breadcrumbs = new List<(string Title, string Url)> { ("Home", "/") }; + + for (int i = 0; i < moodleCategories.Count; i++) + { + string nodeUrl = $"/catalogue/{catalogueUrl}/courses"; + if (i > 0) + { + nodeUrl += $"?moodleCategoryId={moodleCategories[i].Id}"; + } + + breadcrumbs.Add((moodleCategories[i].Name, nodeUrl)); + } + + return breadcrumbs; + } } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs index e7770fd86..42f4ecb39 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Entities.Hierarchy; using LearningHub.Nhs.Models.Validation; /// diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs new file mode 100644 index 000000000..37e87d072 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.WebUI.Interfaces +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.Validation; + + /// + /// Defines the . + /// + public interface ICategoryService + { + /// + /// GetCatalogue version category. + /// + /// catalogueNodeVersionId. + /// A representing the result of the asynchronous operation. + Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId); + + /// + /// GetCoursesByCategoryIdAsync. + /// + /// categoryId. + /// A representing the result of the asynchronous operation. + Task GetCoursesByCategoryIdAsync(int categoryId); + + /// + /// GetSubCategoryByCategoryIdAsync. + /// + /// categoryId. + /// A representing the result of the asynchronous operation. + Task> GetSubCategoryByCategoryIdAsync(int categoryId); + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + Task> GetAllMoodleCategoriesAsync(); + } +} diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index ac23486b8..39df64b2a 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs index b9fd30dca..51c66fde4 100644 --- a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Models.Search; @@ -46,5 +48,20 @@ public class CatalogueIndexViewModel /// Gets or sets the search result view model. /// public SearchResultViewModel SearchResults { get; set; } + + /// + /// Gets or sets the courses in the catalogue. + /// + public List Courses { get; set; } + + /// + /// Gets or sets the courses in the catalogue. + /// + public List SubCategories { get; set; } + + /// + /// Gets or sets the moodle categories. + /// + public List MoodleCategories { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Services/CategoryService.cs b/LearningHub.Nhs.WebUI/Services/CategoryService.cs new file mode 100644 index 000000000..b68a794ca --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/CategoryService.cs @@ -0,0 +1,156 @@ +namespace LearningHub.Nhs.WebUI.Services +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.WebUI.Interfaces; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + + /// + /// The catalogue service. + /// + public class CategoryService : BaseService, ICategoryService + { + private readonly ICacheService cacheService; + + /// + /// Initializes a new instance of the class. + /// + /// The learning hub http client. + /// The Open Api Http Client. + /// The logger. + /// The cacheService. + public CategoryService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, ICacheService cacheService) + : base(learningHubHttpClient, openApiHttpClient, logger) + { + this.cacheService = cacheService; + } + + /// + /// GetCatalogue version category. + /// + /// The catalogueNodeVersionId. + /// A representing the result of the asynchronous operation. + public async Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId) + { + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"category/GetCatalogueVersionCategory/{catalogueNodeVersionId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + var categoryId = 0; + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + categoryId = Convert.ToInt32(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return categoryId; + } + + /// + /// Get sub categories by category id. + /// + /// The categoryId. + /// A representing the result of the asynchronous operation. + public async Task> GetSubCategoryByCategoryIdAsync(int categoryId) + { + List viewmodel = new List { }; + + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"category/GetSubCategoryByCategoryId/{categoryId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + + /// + /// Get courses by category id. + /// + /// The categoryId. + /// A representing the result of the asynchronous operation. + public async Task GetCoursesByCategoryIdAsync(int categoryId) + { + MoodleCoursesResponseModel viewmodel = new MoodleCoursesResponseModel { }; + + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"category/GetCoursesByCategoryId/{categoryId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + public async Task> GetAllMoodleCategoriesAsync() + { + List viewmodel = new List(); + + try + { + var client = await this.OpenApiHttpClient.GetClientAsync(); + + var request = $"Moodle/GetAllMoodleCategories"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + catch (Exception ex) + { + return viewmodel; + } + } + } +} diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index 9fd65bd60..6ed258354 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -82,6 +82,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml new file mode 100644 index 000000000..1cf6ceb8a --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml @@ -0,0 +1,43 @@ +@using LearningHub.Nhs.Models.Enums; +@using LearningHub.Nhs.Models.Moodle; +@using LearningHub.Nhs.Models.Moodle.API +@using LearningHub.Nhs.WebUI.Models.Catalogue; +@using LearningHub.Nhs.WebUI.Helpers; +@model CatalogueIndexViewModel; +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; +@{ + string GetMoodleCourseUrl(int courseId) + { + return moodleApiService.GetCourseUrl(courseId); + } +} +
+
+
+

Courses

+ @foreach (MoodleSubCategoryResponseModel item in Model.SubCategories) + { +
+

+ folder icon + @item.Name +

+
+
+ } + @foreach (Course item in Model.Courses) + { +
+

@item.Displayname

+
+
Type: Course
+
+ @HtmFormatlHelper.StripHtmlTags(item.Summary) +
+
+
+
+ } +
+
+
diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml index 03ef6c173..8c59b46e5 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml @@ -5,6 +5,7 @@ @using LearningHub.Nhs.Models.Enums; @using Microsoft.AspNetCore.Mvc.Routing; @model CatalogueIndexViewModel; + @{ ViewData["Title"] = "Catalogue"; @@ -19,73 +20,79 @@ return IsInRole(RoleEnum.LocalAdmin) || IsInRole(RoleEnum.Editor) || IsInRole(RoleEnum.Reader) || this.User.IsInRole("Administrator"); } - bool IsInRole(RoleEnum role) - { - return Model.UserGroups.Any(x => x.RoleId == (int)role); - } + bool IsInRole(RoleEnum role) + { + return Model.UserGroups.Any(x => x.RoleId == (int)role); + } - string GetBannerUrl() - { - if (!string.IsNullOrEmpty(Model.Catalogue.BannerUrl)) + string GetBannerUrl() { - return GetFileLink(Model.Catalogue.BannerUrl); + if (!string.IsNullOrEmpty(Model.Catalogue.BannerUrl)) + { + return GetFileLink(Model.Catalogue.BannerUrl); + } + return string.Empty; } - return string.Empty; - } - string GetFileLink(string fileName) - { - return "/api/catalogue/download-image/" + Uri.EscapeDataString(fileName); - } + string GetFileLink(string fileName) + { + return "/api/catalogue/download-image/" + Uri.EscapeDataString(fileName); + } + + string GetActiveTabName() + { + switch (ViewBag.ActiveTab) + { + case "courses": + return "Courses"; + case "about": + return "About"; + case "search": + return "Search catalogue"; + case "browse": + default: + return "Browse"; + } + } - string GetActiveTabName() - { - switch (ViewBag.ActiveTab) + List<(string Title, string Url)> breadcrumbs; + if (ViewBag.ActiveTab == "browse") + { + breadcrumbs = UtilityHelper.GetBreadcrumbsForFolderNodes(Model.NodePathNodes.SkipLast(1).ToList(), Model.Catalogue.Url); + } + else if (ViewBag.ActiveTab == "courses") { - case "about": - return "About"; - case "search": - return "Search catalogue"; - case "browse": - default: - return "Browse"; + breadcrumbs = UtilityHelper.GetBreadcrumbsForCourses(Model.MoodleCategories, Model.Catalogue.Url); } - } - - List<(string Title, string Url)> breadcrumbs; - if (ViewBag.ActiveTab == "browse") - { - breadcrumbs = UtilityHelper.GetBreadcrumbsForFolderNodes(Model.NodePathNodes.SkipLast(1).ToList(), Model.Catalogue.Url); - } - else - { - breadcrumbs = new List<(string Title, string Url)> { ("Home", "/") }; - } - - var restrictedAccessVm = new RestrictedAccessBannerViewModel - { - TitleText = "Access to this catalogue is restricted", - BodyText = "This catalogue has been restricted to a limited group of users. You can request access from the catalogue administrator.", - CatalogueNodeVersionId = Model.Catalogue.Id, - RestrictedAccess = Model.Catalogue.RestrictedAccess, - HasCatalogueAccess = Unlocked(), - CatalogueAccessRequest = Model.CatalogueAccessRequest, - UserGroups = Model.UserGroups - }; - - var provider = Model.Catalogue.Providers?.FirstOrDefault(); - var hasBadge = !string.IsNullOrWhiteSpace(Model.Catalogue.BadgeUrl); + else + { + breadcrumbs = new List<(string Title, string Url)> { ("Home", "/") }; + } + + var restrictedAccessVm = new RestrictedAccessBannerViewModel + { + TitleText = "Access to this catalogue is restricted", + BodyText = "This catalogue has been restricted to a limited group of users. You can request access from the catalogue administrator.", + CatalogueNodeVersionId = Model.Catalogue.Id, + RestrictedAccess = Model.Catalogue.RestrictedAccess, + HasCatalogueAccess = Unlocked(), + CatalogueAccessRequest = Model.CatalogueAccessRequest, + UserGroups = Model.UserGroups + }; + + var provider = Model.Catalogue.Providers?.FirstOrDefault(); + var hasBadge = !string.IsNullOrWhiteSpace(Model.Catalogue.BadgeUrl); } -@section styles{ +@section styles { }
- - + + @* Admin banner *@ @if (CanManage()) @@ -98,68 +105,68 @@
} -
- - @* Catalogue header *@ -
- - @if (Model.Catalogue.Hidden) - { -
-
-
Catalogue preview
-
-
- } - @if (ViewBag.ActiveTab == "browse" && Model.NodeDetails != null) - { - @Model.Catalogue.Name - } - -
- @if (provider != null) - { - @provider.Name catalogue badge - } - else if (hasBadge) - { - Provider's catalogue badgeTest - } - -

@(ViewBag.ActiveTab == "browse" && Model.NodeDetails != null ? Model.NodeDetails.Name : Model.Catalogue.Name)

-
- - @if (ViewBag.UserAuthenticated && Model.Catalogue.RestrictedAccess && Unlocked()) - { -

Access granted you have been granted access to view resources in this catalogue

- } - - @if (ViewBag.UserAuthenticated) - { - - } - - @if (!string.IsNullOrEmpty(Model.Catalogue.BannerUrl) && Model.NodeDetails == null) - { - banner image - } +
+ + @* Catalogue header *@ +
+ + @if (Model.Catalogue.Hidden) + { +
+
+
Catalogue preview
+
+
+ } + @if (ViewBag.ActiveTab == "browse" && Model.NodeDetails != null) + { + @Model.Catalogue.Name + } + +
+ @if (provider != null) + { + @provider.Name catalogue badge + } + else if (hasBadge) + { + Provider's catalogue badgeTest + } + +

@(ViewBag.ActiveTab == "browse" && Model.NodeDetails != null ? Model.NodeDetails.Name : Model.Catalogue.Name)

+
- @if (Model.NodeDetails != null && !string.IsNullOrEmpty(Model.NodeDetails.Description)) - { -
- @Html.Raw(Model.NodeDetails.Description) + @if (ViewBag.UserAuthenticated && Model.Catalogue.RestrictedAccess && Unlocked()) + { +

Access granted you have been granted access to view resources in this catalogue

+ } + + @if (ViewBag.UserAuthenticated) + { + + } + + @if (!string.IsNullOrEmpty(Model.Catalogue.BannerUrl) && Model.NodeDetails == null) + { + banner image + } + + @if (Model.NodeDetails != null && !string.IsNullOrEmpty(Model.NodeDetails.Description)) + { +
+ @Html.Raw(Model.NodeDetails.Description) +
+ }
- } -
@* Tab header *@