From 768c6d4b2be4d95b8e524954d75420d3ad33da1c Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Tue, 24 Aug 2021 17:43:45 +0100 Subject: [PATCH 01/12] HEEDLS-563 Add Course Delegates page --- .../DataServices/CourseDataService.cs | 18 +++++ .../CourseDelegatesDataService.cs | 45 +++++++++++ .../Models/CourseDelegates/CourseDelegate.cs | 23 ++++++ .../Models/Courses/Course.cs | 15 ++++ .../Delegates/CourseDelegatesController.cs | 51 +++++++++++++ .../CourseDelegateFilterOptions.cs | 40 ++++++++++ .../Helpers/FilterableTagHelper.cs | 27 +++++++ DigitalLearningSolutions.Web/Startup.cs | 1 + .../delegates/courseDelegates.scss | 11 +++ .../CourseDelegatesViewModel.cs | 30 ++++++++ .../SearchableCourseDelegateViewModel.cs | 35 +++++++++ .../Delegates/CourseDelegates/Index.cshtml | 75 +++++++++++++++++++ .../_SearchableCourseDelegateCard.cshtml | 56 ++++++++++++++ .../Shared/_DelegatesSideNavMenu.cshtml | 5 +- 14 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs create mode 100644 DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs create mode 100644 DigitalLearningSolutions.Data/Models/Courses/Course.cs create mode 100644 DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs create mode 100644 DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs create mode 100644 DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss create mode 100644 DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs create mode 100644 DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml create mode 100644 DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 371ff1c94c..5e102092dd 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -21,6 +21,7 @@ public interface ICourseDataService IEnumerable GetDelegateCoursesInfo(int delegateId); (int totalAttempts, int attemptsPassed) GetDelegateCourseAttemptStats(int delegateId, int customisationId); CourseDetails? GetCourseDetails(int customisationId, int centreId, int categoryId); + IEnumerable GetCoursesAtCentreForCategoryId(int centreId, int categoryId); } public class CourseDataService : ICourseDataService @@ -298,5 +299,22 @@ AND ap.ArchivedDate IS NULL new { customisationId, centreId, categoryId } ).FirstOrDefault(); } + + public IEnumerable GetCoursesAtCentreForCategoryId(int centreId, int categoryId) + { + return connection.Query( + @"SELECT + c.CustomisationID, + c.CentreID, + c.ApplicationID, + a.ApplicationName, + c.CustomisationName + FROM Customisations AS c + JOIN Applications AS a on a.ApplicationID = c.ApplicationID + WHERE (CentreID = @centreId OR CentreID = 0) + AND (a.CourseCategoryID = @categoryId OR @categoryId = 0)", + new { centreId, categoryId } + ); + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs new file mode 100644 index 0000000000..e3de80960b --- /dev/null +++ b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs @@ -0,0 +1,45 @@ +namespace DigitalLearningSolutions.Data.DataServices +{ + using System.Collections.Generic; + using System.Data; + using Dapper; + using DigitalLearningSolutions.Data.Models.CourseDelegates; + + public interface ICourseDelegatesDataService + { + IEnumerable GetDelegatesOnCourse(int customisationId, int centreId); + } + + public class CourseDelegatesDataService : ICourseDelegatesDataService + { + private readonly IDbConnection connection; + + public CourseDelegatesDataService(IDbConnection connection) + { + this.connection = connection; + } + + public IEnumerable GetDelegatesOnCourse(int customisationId, int centreId) + { + return connection.Query( + @"SELECT + c.CandidateID AS DelegateId, + c.CandidateNumber, + c.FirstName, + c.LastName, + c.EmailAddress, + c.Active, + p.ProgressID, + p.PLLocked AS Locked, + p.SubmittedTime AS LastUpdated, + c.DateRegistered AS Enrolled, + p.CompleteByDate AS CompleteBy + FROM Candidates AS c + INNER JOIN Progress AS p ON p.CandidateID = c.CandidateID + WHERE c.CentreID = @centreId + AND p.CustomisationID = @customisationId", + new { customisationId, centreId } + ); + } + } +} diff --git a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs new file mode 100644 index 0000000000..4109316ab8 --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs @@ -0,0 +1,23 @@ +namespace DigitalLearningSolutions.Data.Models.CourseDelegates +{ + using System; + + public class CourseDelegate + { + public int DelegateId { get; set; } + public string CandidateNumber { get; set; } + public string? FirstName { get; set; } + public string LastName { get; set; } + public string? EmailAddress { get; set; } + public bool Active { get; set; } + public int ProgressId { get; set; } + public bool Locked { get; set; } + public DateTime LastUpdated { get; set; } + public DateTime Enrolled { get; set; } + public DateTime? CompleteBy { get; set; } + + public string FullName => (string.IsNullOrEmpty(FirstName) ? "" : $"{FirstName} ") + LastName; + + public string TitleName => FullName + (string.IsNullOrEmpty(EmailAddress) ? "" : $" ({EmailAddress})"); + } +} diff --git a/DigitalLearningSolutions.Data/Models/Courses/Course.cs b/DigitalLearningSolutions.Data/Models/Courses/Course.cs new file mode 100644 index 0000000000..a6eec7152c --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/Courses/Course.cs @@ -0,0 +1,15 @@ +namespace DigitalLearningSolutions.Data.Models.Courses +{ + public class Course + { + public int CustomisationId { get; set; } + public int CentreId { get; set; } + public int ApplicationId { get; set; } + public string ApplicationName { get; set; } + public string CustomisationName { get; set; } + + public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) + ? ApplicationName + : ApplicationName + " - " + CustomisationName; + } +} diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs new file mode 100644 index 0000000000..7aa0ab75d3 --- /dev/null +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs @@ -0,0 +1,51 @@ +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Delegates +{ + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.DataServices.UserDataService; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.FeatureManagement.Mvc; + + [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] + [Authorize(Policy = CustomPolicies.UserCentreAdmin)] + [Route("TrackingSystem/Delegates/CourseDelegates")] + public class CourseDelegatesController : Controller + { + private readonly ICourseDataService courseDataService; + private readonly ICourseDelegatesDataService courseDelegatesDataService; + private readonly IUserDataService userDataService; + + public CourseDelegatesController( + ICourseDataService courseDataService, + IUserDataService userDataService, + ICourseDelegatesDataService courseDelegatesDataService + ) + { + this.courseDataService = courseDataService; + this.userDataService = userDataService; + this.courseDelegatesDataService = courseDelegatesDataService; + } + + public IActionResult Index(int? customisationId = null) + { + var adminId = User.GetAdminId()!.Value; + var centreId = User.GetCentreId(); + var adminUser = userDataService.GetAdminUserById(adminId)!; + + var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, adminUser.CategoryId).ToList(); + + var currentCustomisationId = customisationId ?? courses.First().CustomisationId; + + // TODO: HEEDLS-564 - paginate properly instead of taking 10. + var courseDelegates = courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId, centreId) + .Take(10).ToList(); + + var model = new CourseDelegatesViewModel(courses, courseDelegates, currentCustomisationId); + + return View(model); + } + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs new file mode 100644 index 0000000000..4a215d086c --- /dev/null +++ b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs @@ -0,0 +1,40 @@ +namespace DigitalLearningSolutions.Web.Helpers.FilterOptions +{ + using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Web.Models.Enums; + using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + + public static class CourseDelegateAccountStatusFilterOptions + { + private const string Group = "AccountStatus"; + + public static readonly FilterOptionViewModel Active = new FilterOptionViewModel( + "Active", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Active) + FilteringHelper.Separator + "true", + FilterStatus.Success + ); + + public static readonly FilterOptionViewModel Inactive = new FilterOptionViewModel( + "Inactive", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Active) + FilteringHelper.Separator + "false", + FilterStatus.Warning + ); + } + + public static class CourseDelegateProgressLockedFilterOptions + { + private const string Group = "ProgressLocked"; + + public static readonly FilterOptionViewModel Locked = new FilterOptionViewModel( + "Locked", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Locked) + FilteringHelper.Separator + "true", + FilterStatus.Success + ); + + public static readonly FilterOptionViewModel NotLocked = new FilterOptionViewModel( + "Not locked", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Locked) + FilteringHelper.Separator + "false", + FilterStatus.Default + ); + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs index 6a561840b9..d84a841392 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs @@ -1,10 +1,12 @@ namespace DigitalLearningSolutions.Web.Helpers { using System.Collections.Generic; + using DigitalLearningSolutions.Data.Models.CourseDelegates; using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers.FilterOptions; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + using FluentMigrator.Runner.Extensions; public static class FilterableTagHelper { @@ -80,5 +82,30 @@ CourseStatistics courseStatistics return tags; } + + public static IEnumerable GetCurrentTagsForCourseDelegate(CourseDelegate courseDelegate) + { + var tags = new List(); + + if (courseDelegate.Active) + { + tags.Add(new SearchableTagViewModel(CourseDelegateAccountStatusFilterOptions.Active)); + } + else + { + tags.Add(new SearchableTagViewModel(CourseDelegateAccountStatusFilterOptions.Inactive)); + } + + if (courseDelegate.Locked) + { + tags.Add(new SearchableTagViewModel(CourseDelegateProgressLockedFilterOptions.Locked)); + } + else + { + tags.Add(new SearchableTagViewModel(CourseDelegateProgressLockedFilterOptions.NotLocked, true)); + } + + return tags; + } } } diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index 705deadac5..d7fc50e6b7 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -197,6 +197,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); RegisterWebServiceFilters(services); } diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss new file mode 100644 index 0000000000..072ac65aa6 --- /dev/null +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss @@ -0,0 +1,11 @@ +@import '../../shared/searchableElements/searchableElements'; +@import '../../shared/headingButtons'; + +.course-dropdown { + @extend .input-with-submit-button; + width: 63%; + + @include mq($from: $xl-desktop) { + width: 80%; + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs new file mode 100644 index 0000000000..b40d7ee182 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs @@ -0,0 +1,30 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Web.Helpers; + using Microsoft.AspNetCore.Mvc.Rendering; + + public class CourseDelegatesViewModel + { + public CourseDelegatesViewModel( + List courses, + List courseDelegates, + int currentCustomisationId + ) + { + CustomisationId = currentCustomisationId; + var courseOptions = courses.Select(c => (c.CustomisationId, c.CourseName)); + Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, currentCustomisationId); + + Delegates = courseDelegates.Select(cd => new SearchableCourseDelegateViewModel(cd)); + } + + public int CustomisationId { get; set; } + public IEnumerable Courses { get; set; } + + public IEnumerable Delegates { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs new file mode 100644 index 0000000000..e3619ce9d3 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs @@ -0,0 +1,35 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates +{ + using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + + public class SearchableCourseDelegateViewModel : BaseFilterableViewModel + { + private const string DateFormat = "dd/MM/yyyy hh:mm"; + + public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) + { + DelegateId = courseDelegate.DelegateId; + CandidateNumber = courseDelegate.CandidateNumber; + TitleName = courseDelegate.TitleName; + Active = courseDelegate.Active; + ProgressId = courseDelegate.ProgressId; + Locked = courseDelegate.Locked; + LastUpdated = courseDelegate.LastUpdated.ToString(DateFormat); + Enrolled = courseDelegate.Enrolled.ToString(DateFormat); + CompleteBy = courseDelegate.CompleteBy?.ToString(DateFormat); + Tags = FilterableTagHelper.GetCurrentTagsForCourseDelegate(courseDelegate); + } + + public int DelegateId { get; set; } + public string CandidateNumber { get; set; } + public string TitleName { get; set; } + public bool Active { get; set; } + public int ProgressId { get; set; } + public bool Locked { get; set; } + public string LastUpdated { get; set; } + public string Enrolled { get; set; } + public string? CompleteBy { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml new file mode 100644 index 0000000000..bfde94ae8c --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -0,0 +1,75 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Models.Enums +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates +@using Microsoft.Extensions.Configuration +@model CourseDelegatesViewModel + + + +@{ + ViewData["Title"] = "Course delegates"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +
+
+ +
+ +
+
+
+

Course delegates

+
+ +
+ +
+
+
+ + + +
+
+
+ +
+
+ @if (!Model.Delegates.Any()) + { + + } + else + { +
+ @foreach (var groupModel in Model.Delegates) + { + + } +
+ } +
+
+ + +
+
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml new file mode 100644 index 0000000000..2e2e508954 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml @@ -0,0 +1,56 @@ +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates +@model SearchableCourseDelegateViewModel + +
+
+ + + @Model.TitleName + + +
+ + + +
+
+
+ Delegate ID +
+
+ @Model.CandidateNumber +
+
+
+
+ Last updated +
+
+ @Model.LastUpdated +
+
+
+
+ Enrolled +
+
+ @Model.Enrolled +
+
+
+
+ Complete by +
+ +
+
+ + + View progress + + + View learning log + +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegatesSideNavMenu.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegatesSideNavMenu.cshtml index 8bb0913e0f..c49c724ec9 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegatesSideNavMenu.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegatesSideNavMenu.cshtml @@ -19,8 +19,9 @@ link-text="Delegate groups" is-current-page="Model == DelegatePage.DelegateGroups" /> - From 2f2e52701f22132dc9925c6dde103827eca1804f Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Wed, 25 Aug 2021 13:20:29 +0100 Subject: [PATCH 02/12] HEEDLS-563 Refactor logic to work with no courses due to permissions --- .../DataServices/CourseDataService.cs | 3 +- .../CourseDelegatesDataService.cs | 3 +- .../Models/CourseDelegates/CourseDelegate.cs | 1 + .../Models/Courses/Course.cs | 1 + .../Delegates/CourseDelegatesController.cs | 11 +-- .../CourseDelegatesViewModel.cs | 7 +- .../SearchableCourseDelegateViewModel.cs | 2 + .../Delegates/CourseDelegates/Index.cshtml | 68 +++++++++++-------- .../_SearchableCourseDelegateCard.cshtml | 10 ++- 9 files changed, 68 insertions(+), 38 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 5e102092dd..0e5c8a5101 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -308,7 +308,8 @@ public IEnumerable GetCoursesAtCentreForCategoryId(int centreId, int cat c.CentreID, c.ApplicationID, a.ApplicationName, - c.CustomisationName + c.CustomisationName, + c.Active FROM Customisations AS c JOIN Applications AS a on a.ApplicationID = c.ApplicationID WHERE (CentreID = @centreId OR CentreID = 0) diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs index e3de80960b..c208240b50 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs @@ -33,7 +33,8 @@ public IEnumerable GetDelegatesOnCourse(int customisationId, int p.PLLocked AS Locked, p.SubmittedTime AS LastUpdated, c.DateRegistered AS Enrolled, - p.CompleteByDate AS CompleteBy + p.CompleteByDate AS CompleteBy, + p.RemovedDate FROM Candidates AS c INNER JOIN Progress AS p ON p.CandidateID = c.CandidateID WHERE c.CentreID = @centreId diff --git a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs index 4109316ab8..e177616169 100644 --- a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs +++ b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs @@ -15,6 +15,7 @@ public class CourseDelegate public DateTime LastUpdated { get; set; } public DateTime Enrolled { get; set; } public DateTime? CompleteBy { get; set; } + public DateTime? RemovedDate { get; set; } public string FullName => (string.IsNullOrEmpty(FirstName) ? "" : $"{FirstName} ") + LastName; diff --git a/DigitalLearningSolutions.Data/Models/Courses/Course.cs b/DigitalLearningSolutions.Data/Models/Courses/Course.cs index a6eec7152c..7518d33159 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/Course.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/Course.cs @@ -7,6 +7,7 @@ public class Course public int ApplicationId { get; set; } public string ApplicationName { get; set; } public string CustomisationName { get; set; } + public bool Active { get; set; } public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) ? ApplicationName diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs index 7aa0ab75d3..0a6dc9ae6f 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs @@ -1,8 +1,10 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Delegates { + using System.Collections.Generic; using System.Linq; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.DataServices.UserDataService; + using DigitalLearningSolutions.Data.Models.CourseDelegates; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates; using Microsoft.AspNetCore.Authorization; @@ -37,12 +39,13 @@ public IActionResult Index(int? customisationId = null) var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, adminUser.CategoryId).ToList(); - var currentCustomisationId = customisationId ?? courses.First().CustomisationId; + var currentCustomisationId = customisationId ?? courses.FirstOrDefault()?.CustomisationId; // TODO: HEEDLS-564 - paginate properly instead of taking 10. - var courseDelegates = courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId, centreId) - .Take(10).ToList(); - + var courseDelegates = currentCustomisationId.HasValue + ? courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId.Value, centreId) + .Take(10).ToList() + : new List(); var model = new CourseDelegatesViewModel(courses, courseDelegates, currentCustomisationId); return View(model); diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs index b40d7ee182..bf1a7fbc29 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs @@ -12,18 +12,21 @@ public class CourseDelegatesViewModel public CourseDelegatesViewModel( List courses, List courseDelegates, - int currentCustomisationId + int? currentCustomisationId ) { CustomisationId = currentCustomisationId; var courseOptions = courses.Select(c => (c.CustomisationId, c.CourseName)); Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, currentCustomisationId); + Active = courses.SingleOrDefault(c => c.CustomisationId == currentCustomisationId)?.Active; + Delegates = courseDelegates.Select(cd => new SearchableCourseDelegateViewModel(cd)); } - public int CustomisationId { get; set; } + public int? CustomisationId { get; set; } public IEnumerable Courses { get; set; } + public bool? Active { get; set; } public IEnumerable Delegates { get; set; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs index e3619ce9d3..1961c0c285 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs @@ -19,6 +19,7 @@ public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) LastUpdated = courseDelegate.LastUpdated.ToString(DateFormat); Enrolled = courseDelegate.Enrolled.ToString(DateFormat); CompleteBy = courseDelegate.CompleteBy?.ToString(DateFormat); + RemovedDate = courseDelegate.RemovedDate?.ToString(DateFormat); Tags = FilterableTagHelper.GetCurrentTagsForCourseDelegate(courseDelegate); } @@ -31,5 +32,6 @@ public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) public string LastUpdated { get; set; } public string Enrolled { get; set; } public string? CompleteBy { get; set; } + public string? RemovedDate { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml index bfde94ae8c..36f7e0d372 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -36,37 +36,49 @@ -
-
-
- - - -
-
-
+ @if (Model.Courses.Any()) { +
+
+
+ + + +
+
+
-
-
- @if (!Model.Delegates.Any()) - { - - } - else - { -
- @foreach (var groupModel in Model.Delegates) - { - - } + @if (!Model.Active == true) { +
+
+ The currently selected course is inactive.
- } +
+ } + +
+
+ @if (!Model.Delegates.Any()) { + + } else { +
+ @foreach (var groupModel in Model.Delegates) { + + } +
+ } +
-
+ } else { +
+
+ There are no courses within the category you can view. +
+
+ }
+
+
+ Removed date +
+ +
- + View progress - + View learning log
From 1f611a39721971dd488dcd04eb827307b600f01c Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Thu, 26 Aug 2021 09:06:06 +0100 Subject: [PATCH 03/12] HEEDLS-563 Small layout fix and tag colour --- .../Helpers/FilterOptions/CourseDelegateFilterOptions.cs | 2 +- .../TrackingSystem/Delegates/CourseDelegates/Index.cshtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs index 4a215d086c..e473d3e9fc 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs @@ -28,7 +28,7 @@ public static class CourseDelegateProgressLockedFilterOptions public static readonly FilterOptionViewModel Locked = new FilterOptionViewModel( "Locked", Group + FilteringHelper.Separator + nameof(CourseDelegate.Locked) + FilteringHelper.Separator + "true", - FilterStatus.Success + FilterStatus.Warning ); public static readonly FilterOptionViewModel NotLocked = new FilterOptionViewModel( diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml index 36f7e0d372..0f5d01dfef 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -52,7 +52,7 @@ @if (!Model.Active == true) {
- The currently selected course is inactive. +

The currently selected course is inactive.

} @@ -75,7 +75,7 @@ } else {
- There are no courses within the category you can view. +

There are no courses within the category you can view.

} From 062732cb6ffd9f45d60ff7921c682d5f020bb947 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Thu, 26 Aug 2021 13:32:57 +0100 Subject: [PATCH 04/12] HEEDLS-563 Add unit tests --- .../DataServices/CourseDataServiceTests.cs | 26 +++++ .../CourseDelegatesDataServiceTests.cs | 54 +++++++++ .../Services/CourseDelegatesServiceTests.cs | 85 +++++++++++++++ .../CourseDelegates/CourseDelegatesData.cs | 25 +++++ .../Services/CourseDelegatesService.cs | 55 ++++++++++ .../BasicAuthenticatedAccessibilityTests.cs | 6 +- .../Delegates/CourseDelegatesController.cs | 32 ++---- DigitalLearningSolutions.Web/Startup.cs | 103 ++++++++++-------- .../CourseDelegatesViewModel.cs | 18 ++- 9 files changed, 321 insertions(+), 83 deletions(-) create mode 100644 DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs create mode 100644 DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs create mode 100644 DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegatesData.cs create mode 100644 DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index c6234d0fa6..8c24a88d9a 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -9,6 +9,7 @@ namespace DigitalLearningSolutions.Data.Tests.DataServices using DigitalLearningSolutions.Data.Tests.TestHelpers; using FakeItEasy; using FluentAssertions; + using FluentAssertions.Execution; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -316,5 +317,30 @@ public void GetDelegateCoursesAttemptStats_should_return_delegate_course_info_co totalAttempts.Should().Be(23); attemptsPassed.Should().Be(11); } + + [Test] + public void GetCoursesAtCentreForCategoryId() + { + // Given + var expectedFirstCourse = new Course + { + CustomisationId = 1, + CentreId = 2, + ApplicationId = 1, + ApplicationName = "Entry Level - Win XP, Office 2003/07 OLD", + CustomisationName = "Standard", + Active = false + }; + + // When + var result = courseDataService.GetCoursesAtCentreForCategoryId(2, 0).ToList(); + + // Then + using (new AssertionScope()) + { + result.Should().HaveCount(69); + result.First().Should().BeEquivalentTo(expectedFirstCourse); + } + } } } diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs new file mode 100644 index 0000000000..fc5d3e2e0d --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs @@ -0,0 +1,54 @@ +namespace DigitalLearningSolutions.Data.Tests.DataServices +{ + using System; + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Data.Tests.TestHelpers; + using FluentAssertions; + using FluentAssertions.Execution; + using NUnit.Framework; + + public class CourseDelegatesDataServiceTests + { + private ICourseDelegatesDataService courseDelegatesDataService = null!; + + [SetUp] + public void Setup() + { + var connection = ServiceTestHelper.GetDatabaseConnection(); + courseDelegatesDataService = new CourseDelegatesDataService(connection); + } + + [Test] + public void GetDelegatesOnCourse_returns_expected_values() + { + // Given + var expectedFirstRecord = new CourseDelegate + { + Active = true, + CandidateNumber = "PC97", + CompleteBy = null, + DelegateId = 32926, + EmailAddress = "erpock.hs@5bntu", + Enrolled = new DateTime(2012, 07, 02, 13, 30, 37, 807), + FirstName = "xxxxx", + LastName = "xxxx", + LastUpdated = new DateTime(2012, 07, 31, 10, 18, 39, 993), + Locked = false, + ProgressId = 18395, + RemovedDate = null + }; + + // When + var result = courseDelegatesDataService.GetDelegatesOnCourse(1, 2).ToList(); + + // Then + using (new AssertionScope()) + { + result.Should().HaveCount(3); + result.First().Should().BeEquivalentTo(expectedFirstRecord); + } + } + } +} diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs new file mode 100644 index 0000000000..7a23fb14fa --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs @@ -0,0 +1,85 @@ +namespace DigitalLearningSolutions.Data.Tests.Services +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.DataServices.UserDataService; + using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Data.Tests.TestHelpers; + using FakeItEasy; + using FluentAssertions; + using FluentAssertions.Execution; + using NUnit.Framework; + + public class CourseDelegatesServiceTests + { + private ICourseDataService courseDataService = null!; + private ICourseDelegatesDataService courseDelegatesDataService = null!; + private ICourseDelegatesService courseDelegatesService = null!; + private IUserDataService userDataService = null!; + + [SetUp] + public void Setup() + { + courseDataService = A.Fake(); + courseDelegatesDataService = A.Fake(); + userDataService = A.Fake(); + + courseDelegatesService = new CourseDelegatesService( + courseDataService, + userDataService, + courseDelegatesDataService + ); + } + + [Test] + public void GetCoursesAndCourseDelegatesForCentre_expected_values() + { + // Given + var adminUser = UserTestHelper.GetDefaultAdminUser(); + A.CallTo(() => userDataService.GetAdminUserById(adminUser.Id)).Returns(adminUser); + A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(adminUser.CentreId, adminUser.CategoryId)) + .Returns(new List { new Course { CustomisationId = 1 } }); + A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) + .Returns(new List { new CourseDelegate() }); + + // When + var result = courseDelegatesService.GetCoursesAndCourseDelegatesForCentre( + adminUser.CentreId, + adminUser.Id, + null + ); + + // Then + using (new AssertionScope()) + { + result.Courses.Should().HaveCount(1); + result.Delegates.Should().HaveCount(1); + result.CustomisationId.Should().Be(1); + } + } + + [Test] + public void GetCoursesAndCourseDelegatesForCentre_contains_empty_lists_with_no_courses_in_category() + { + // Given + var adminUser = UserTestHelper.GetDefaultAdminUser(); + A.CallTo(() => userDataService.GetAdminUserById(A._)).Returns(adminUser); + A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(2, 7)).Returns(new List()); + + // When + var result = courseDelegatesService.GetCoursesAndCourseDelegatesForCentre(2, 7, null); + + // Then + using (new AssertionScope()) + { + A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) + .MustNotHaveHappened(); + result.Courses.Should().BeEmpty(); + result.Delegates.Should().BeEmpty(); + result.CustomisationId.Should().BeNull(); + } + } + } +} diff --git a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegatesData.cs b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegatesData.cs new file mode 100644 index 0000000000..d10d489cb5 --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegatesData.cs @@ -0,0 +1,25 @@ +namespace DigitalLearningSolutions.Data.Models.CourseDelegates +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.Models.Courses; + + public class CourseDelegatesData + { + public CourseDelegatesData( + int? customisationId, + IEnumerable courses, + IEnumerable delegates + ) + { + CustomisationId = customisationId; + Courses = courses; + Delegates = delegates; + } + + public int? CustomisationId { get; set; } + + public IEnumerable Courses { get; set; } + + public IEnumerable Delegates { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs new file mode 100644 index 0000000000..039555d239 --- /dev/null +++ b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs @@ -0,0 +1,55 @@ +namespace DigitalLearningSolutions.Data.Services +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.DataServices.UserDataService; + using DigitalLearningSolutions.Data.Models.CourseDelegates; + + public interface ICourseDelegatesService + { + CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( + int centreId, + int adminId, + int? customisationId + ); + } + + public class CourseDelegatesService : ICourseDelegatesService + { + private readonly ICourseDataService courseDataService; + private readonly ICourseDelegatesDataService courseDelegatesDataService; + private readonly IUserDataService userDataService; + + public CourseDelegatesService( + ICourseDataService courseDataService, + IUserDataService userDataService, + ICourseDelegatesDataService courseDelegatesDataService + ) + { + this.courseDataService = courseDataService; + this.userDataService = userDataService; + this.courseDelegatesDataService = courseDelegatesDataService; + } + + public CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( + int centreId, + int adminId, + int? customisationId + ) + { + var adminUser = userDataService.GetAdminUserById(adminId)!; + + var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, adminUser.CategoryId).ToList(); + + var currentCustomisationId = customisationId ?? courses.FirstOrDefault()?.CustomisationId; + + var courseDelegates = currentCustomisationId.HasValue + ? courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId.Value, centreId) + .ToList() + : new List(); + + return new CourseDelegatesData(currentCustomisationId, courses, courseDelegates); + } + } +} diff --git a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs index 53664c6094..e8ecb70f13 100644 --- a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs +++ b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs @@ -43,11 +43,15 @@ public BasicAuthenticatedAccessibilityTests(AuthenticatedAccessibilityTestsFixtu [InlineData("/TrackingSystem/Delegates/All", "Delegates")] [InlineData("/TrackingSystem/Delegates/Groups", "Groups")] [InlineData("/TrackingSystem/Delegates/Groups/5/Delegates", "Group delegates")] - [InlineData("/TrackingSystem/Delegates/Groups/5/Delegates/Remove/245969", "Are you sure you would like to remove xxxxx xxxx from this group?")] + [InlineData( + "/TrackingSystem/Delegates/Groups/5/Delegates/Remove/245969", + "Are you sure you would like to remove xxxxx xxxx from this group?" + )] [InlineData("/TrackingSystem/Delegates/Groups/5/Courses", "Group courses")] [InlineData("/TrackingSystem/Delegates/View/3", "xxxx xxxxxx")] [InlineData("/TrackingSystem/Delegates/Approve", "Approve delegate registrations")] [InlineData("/TrackingSystem/Delegates/BulkUpload", "Bulk upload/update delegates")] + [InlineData("/TrackingSystem/Delegates/CourseDelegates", "Course delegates")] [InlineData("/NotificationPreferences", "Notification preferences")] [InlineData("/NotificationPreferences/Edit/AdminUser", "Update notification preferences")] [InlineData("/NotificationPreferences/Edit/DelegateUser", "Update notification preferences")] diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs index 0a6dc9ae6f..72f26822e0 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs @@ -1,10 +1,6 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Delegates { - using System.Collections.Generic; - using System.Linq; - using DigitalLearningSolutions.Data.DataServices; - using DigitalLearningSolutions.Data.DataServices.UserDataService; - using DigitalLearningSolutions.Data.Models.CourseDelegates; + using DigitalLearningSolutions.Data.Services; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates; using Microsoft.AspNetCore.Authorization; @@ -16,37 +12,23 @@ [Route("TrackingSystem/Delegates/CourseDelegates")] public class CourseDelegatesController : Controller { - private readonly ICourseDataService courseDataService; - private readonly ICourseDelegatesDataService courseDelegatesDataService; - private readonly IUserDataService userDataService; + private readonly ICourseDelegatesService courseDelegatesService; public CourseDelegatesController( - ICourseDataService courseDataService, - IUserDataService userDataService, - ICourseDelegatesDataService courseDelegatesDataService + ICourseDelegatesService courseDelegatesService ) { - this.courseDataService = courseDataService; - this.userDataService = userDataService; - this.courseDelegatesDataService = courseDelegatesDataService; + this.courseDelegatesService = courseDelegatesService; } public IActionResult Index(int? customisationId = null) { var adminId = User.GetAdminId()!.Value; var centreId = User.GetCentreId(); - var adminUser = userDataService.GetAdminUserById(adminId)!; + var courseDelegatesData = + courseDelegatesService.GetCoursesAndCourseDelegatesForCentre(centreId, adminId, customisationId); - var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, adminUser.CategoryId).ToList(); - - var currentCustomisationId = customisationId ?? courses.FirstOrDefault()?.CustomisationId; - - // TODO: HEEDLS-564 - paginate properly instead of taking 10. - var courseDelegates = currentCustomisationId.HasValue - ? courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId.Value, centreId) - .Take(10).ToList() - : new List(); - var model = new CourseDelegatesViewModel(courses, courseDelegates, currentCustomisationId); + var model = new CourseDelegatesViewModel(courseDelegatesData); return View(model); } diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index d7fc50e6b7..3d5e4ff75c 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -140,65 +140,76 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(_ => new SqlConnection(defaultConnectionString)); // Register services. + RegisterServices(services); + RegisterDataServices(services); + RegisterWebServiceFilters(services); + } + + private static void RegisterServices(IServiceCollection services) + { + services.AddHttpClient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddHttpClient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + } + + private static void RegisterDataServices(IServiceCollection services) + { services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); - RegisterWebServiceFilters(services); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void RegisterWebServiceFilters(IServiceCollection services) diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs index bf1a7fbc29..66760ff4f4 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs @@ -3,25 +3,21 @@ using System.Collections.Generic; using System.Linq; using DigitalLearningSolutions.Data.Models.CourseDelegates; - using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Web.Helpers; using Microsoft.AspNetCore.Mvc.Rendering; public class CourseDelegatesViewModel { - public CourseDelegatesViewModel( - List courses, - List courseDelegates, - int? currentCustomisationId - ) + public CourseDelegatesViewModel(CourseDelegatesData courseDelegatesData) { - CustomisationId = currentCustomisationId; - var courseOptions = courses.Select(c => (c.CustomisationId, c.CourseName)); - Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, currentCustomisationId); + CustomisationId = courseDelegatesData.CustomisationId; + var courseOptions = courseDelegatesData.Courses.Select(c => (c.CustomisationId, c.CourseName)); + Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, CustomisationId); - Active = courses.SingleOrDefault(c => c.CustomisationId == currentCustomisationId)?.Active; + Active = courseDelegatesData.Courses.SingleOrDefault(c => c.CustomisationId == CustomisationId)?.Active; - Delegates = courseDelegates.Select(cd => new SearchableCourseDelegateViewModel(cd)); + // TODO: HEEDLS-564 - paginate properly instead of taking 10. + Delegates = courseDelegatesData.Delegates.Take(10).Select(cd => new SearchableCourseDelegateViewModel(cd)); } public int? CustomisationId { get; set; } From 8d7b6017fd0788f691f1e12a9f2e2f3d546c0b65 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Thu, 26 Aug 2021 14:35:37 +0100 Subject: [PATCH 05/12] HEEDLS-563 Fix unit test with different result on test --- .../DataServices/CourseDataServiceTests.cs | 4 ++-- .../TrackingSystem/Delegates/CourseDelegates/Index.cshtml | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index 8c24a88d9a..41c8342894 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -319,7 +319,7 @@ public void GetDelegateCoursesAttemptStats_should_return_delegate_course_info_co } [Test] - public void GetCoursesAtCentreForCategoryId() + public void GetCoursesAtCentreForCategoryId_returns_expected_values() { // Given var expectedFirstCourse = new Course @@ -339,7 +339,7 @@ public void GetCoursesAtCentreForCategoryId() using (new AssertionScope()) { result.Should().HaveCount(69); - result.First().Should().BeEquivalentTo(expectedFirstCourse); + result.First(c => c.CustomisationId == 1).Should().BeEquivalentTo(expectedFirstCourse); } } } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml index 0f5d01dfef..f3b12c1819 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -41,7 +41,10 @@
- + From 3ae189758b58a3419163028a876cfc9d9dd61175 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Wed, 1 Sep 2021 11:59:40 +0100 Subject: [PATCH 06/12] HEEDLS-563 Review markups --- .../DataServices/CourseDataServiceTests.cs | 1 + .../Models/CourseStatisticTests.cs | 29 -------------- .../Models/User/CourseTests.cs | 37 +++++++++++++++++ .../Services/CourseDelegatesServiceTests.cs | 40 ++++++++++++++----- .../DataServices/CourseDataService.cs | 1 + .../Helpers/DateHelper.cs | 2 - .../Models/Courses/Course.cs | 8 +++- .../Models/Courses/CourseStatistics.cs | 17 +------- .../Services/CourseDelegatesService.cs | 12 ++---- .../Delegates/CourseDelegatesController.cs | 4 +- .../Helpers/DateHelper.cs | 9 +++++ .../Helpers/FilterableTagHelper.cs | 1 - .../SummaryViewModel.cs | 3 +- .../UnacknowledgedNotificationViewModel.cs | 2 +- .../CourseDetails/CourseSummaryViewModel.cs | 5 ++- .../SearchableCourseDelegateViewModel.cs | 10 ++--- .../Delegates/DelegateCourseInfoViewModel.cs | 13 +++--- .../DelegateGroups/GroupCourseViewModel.cs | 2 +- .../Delegates/DelegateInfoViewModel.cs | 3 +- .../_SearchableCourseDelegateCard.cshtml | 5 +++ 20 files changed, 114 insertions(+), 90 deletions(-) create mode 100644 DigitalLearningSolutions.Data.Tests/Models/User/CourseTests.cs create mode 100644 DigitalLearningSolutions.Web/Helpers/DateHelper.cs diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index 41c8342894..b7197db1ba 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -235,6 +235,7 @@ public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statis CentreId = 101, Active = false, AllCentres = false, + ApplicationId = 1, ApplicationName = "Entry Level - Win XP, Office 2003/07 OLD", CustomisationName = "Standard", DelegateCount = 25, diff --git a/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs index f659460cb7..66a98005f3 100644 --- a/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs @@ -33,34 +33,5 @@ public void InProgressCount_should_be_calculated_from_total_delegates_and_total_ // Then courseStatistics.InProgressCount.Should().Be(42); } - - [Test] - public void Course_name_should_be_application_name_if_customisation_name_is_null() - { - // When - var courseStatistics = new CourseStatistics - { - ApplicationName = "Test application", - CustomisationName = null, - }; - - // Then - courseStatistics.CourseName.Should().BeEquivalentTo("Test application"); - } - - [Test] - public void Course_name_should_include_customisation_name_if_it_is_not_null() - { - // When - var courseStatistics = new CourseStatistics - { - ApplicationName = "Test application", - CustomisationName = "customisation", - }; - - // Then - courseStatistics.CourseName.Should().BeEquivalentTo("Test application - customisation"); - } - } } diff --git a/DigitalLearningSolutions.Data.Tests/Models/User/CourseTests.cs b/DigitalLearningSolutions.Data.Tests/Models/User/CourseTests.cs new file mode 100644 index 0000000000..e7b3842357 --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/Models/User/CourseTests.cs @@ -0,0 +1,37 @@ +namespace DigitalLearningSolutions.Data.Tests.Models.User +{ + using DigitalLearningSolutions.Data.Models.Courses; + using FluentAssertions; + using NUnit.Framework; + + public class CourseTests + { + [Test] + public void Course_name_should_be_application_name_if_customisation_name_is_null() + { + // When + var courseStatistics = new Course + { + ApplicationName = "Test application", + CustomisationName = string.Empty + }; + + // Then + courseStatistics.CourseName.Should().BeEquivalentTo("Test application"); + } + + [Test] + public void Course_name_should_include_customisation_name_if_it_is_not_null() + { + // When + var courseStatistics = new Course + { + ApplicationName = "Test application", + CustomisationName = "customisation" + }; + + // Then + courseStatistics.CourseName.Should().BeEquivalentTo("Test application - customisation"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs index 7a23fb14fa..288fc58193 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs @@ -2,7 +2,6 @@ { using System.Collections.Generic; using DigitalLearningSolutions.Data.DataServices; - using DigitalLearningSolutions.Data.DataServices.UserDataService; using DigitalLearningSolutions.Data.Models.CourseDelegates; using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Services; @@ -17,18 +16,15 @@ public class CourseDelegatesServiceTests private ICourseDataService courseDataService = null!; private ICourseDelegatesDataService courseDelegatesDataService = null!; private ICourseDelegatesService courseDelegatesService = null!; - private IUserDataService userDataService = null!; [SetUp] public void Setup() { courseDataService = A.Fake(); courseDelegatesDataService = A.Fake(); - userDataService = A.Fake(); courseDelegatesService = new CourseDelegatesService( courseDataService, - userDataService, courseDelegatesDataService ); } @@ -37,17 +33,17 @@ public void Setup() public void GetCoursesAndCourseDelegatesForCentre_expected_values() { // Given - var adminUser = UserTestHelper.GetDefaultAdminUser(); - A.CallTo(() => userDataService.GetAdminUserById(adminUser.Id)).Returns(adminUser); - A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(adminUser.CentreId, adminUser.CategoryId)) + const int centreId = 2; + const int categoryId = 1; + A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId)) .Returns(new List { new Course { CustomisationId = 1 } }); A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) .Returns(new List { new CourseDelegate() }); // When var result = courseDelegatesService.GetCoursesAndCourseDelegatesForCentre( - adminUser.CentreId, - adminUser.Id, + centreId, + categoryId, null ); @@ -64,8 +60,6 @@ public void GetCoursesAndCourseDelegatesForCentre_expected_values() public void GetCoursesAndCourseDelegatesForCentre_contains_empty_lists_with_no_courses_in_category() { // Given - var adminUser = UserTestHelper.GetDefaultAdminUser(); - A.CallTo(() => userDataService.GetAdminUserById(A._)).Returns(adminUser); A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(2, 7)).Returns(new List()); // When @@ -81,5 +75,29 @@ public void GetCoursesAndCourseDelegatesForCentre_contains_empty_lists_with_no_c result.CustomisationId.Should().BeNull(); } } + + [Test] + public void GetCoursesAndCourseDelegatesForCentre_uses_passed_in_customisation_id() + { + // Given + const int customisationId = 2; + const int centreId = 2; + const int categoryId = 1; + A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId)) + .Returns(new List { new Course { CustomisationId = 1 } }); + A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) + .Returns(new List { new CourseDelegate() }); + + // When + var result = courseDelegatesService.GetCoursesAndCourseDelegatesForCentre( + centreId, + categoryId, + customisationId + ); + + // Then + A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(customisationId, centreId)) + .MustHaveHappened(); + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 0e5c8a5101..d799046217 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -180,6 +180,7 @@ public IEnumerable GetCourseStatisticsAtCentreForCategoryId(in cu.CentreID, cu.Active, cu.AllCentres, + ap.ApplicationId, ap.ApplicationName, cu.CustomisationName, {DelegateCountQuery}, diff --git a/DigitalLearningSolutions.Data/Helpers/DateHelper.cs b/DigitalLearningSolutions.Data/Helpers/DateHelper.cs index b8d01db442..6d5614856a 100644 --- a/DigitalLearningSolutions.Data/Helpers/DateHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/DateHelper.cs @@ -6,8 +6,6 @@ public static class DateHelper { - public static string StandardDateFormat = "dd/MM/yyyy"; - public static IEnumerable<(int Month, int Year)> GetMonthsAndYearsBetweenDates(DateTime startDate, DateTime endDate) { var diffInMonths = (endDate.Year - startDate.Year) * 12 + (endDate.Month - startDate.Month); diff --git a/DigitalLearningSolutions.Data/Models/Courses/Course.cs b/DigitalLearningSolutions.Data/Models/Courses/Course.cs index 7518d33159..65e844edce 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/Course.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/Course.cs @@ -1,6 +1,6 @@ namespace DigitalLearningSolutions.Data.Models.Courses { - public class Course + public class Course : BaseSearchableItem { public int CustomisationId { get; set; } public int CentreId { get; set; } @@ -12,5 +12,11 @@ public class Course public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) ? ApplicationName : ApplicationName + " - " + CustomisationName; + + public override string SearchableName + { + get => SearchableNameOverrideForFuzzySharp ?? CourseName; + set => SearchableNameOverrideForFuzzySharp = value; + } } } diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs index 12f2aabdf8..95283f9fe6 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs @@ -2,14 +2,9 @@ { using System; - public class CourseStatistics : BaseSearchableItem + public class CourseStatistics : Course { - public int CustomisationId { get; set; } - public int CentreId { get; set; } - public bool Active { get; set; } public bool AllCentres { get; set; } - public string ApplicationName { get; set; } - public string? CustomisationName { get; set; } public int DelegateCount { get; set; } public int CompletedCount { get; set; } public int InProgressCount => DelegateCount - CompletedCount; @@ -20,16 +15,6 @@ public class CourseStatistics : BaseSearchableItem public string CourseTopic { get; set; } public string LearningMinutes { get; set; } - public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) - ? ApplicationName - : ApplicationName + " - " + CustomisationName; - public double PassRate => AllAttempts == 0 ? 0 : Math.Round(100 * AttemptsPassed / (double)AllAttempts); - - public override string SearchableName - { - get => SearchableNameOverrideForFuzzySharp ?? CourseName; - set => SearchableNameOverrideForFuzzySharp = value; - } } } diff --git a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs index 039555d239..df4d0ab1df 100644 --- a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Linq; using DigitalLearningSolutions.Data.DataServices; - using DigitalLearningSolutions.Data.DataServices.UserDataService; using DigitalLearningSolutions.Data.Models.CourseDelegates; public interface ICourseDelegatesService { CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( int centreId, - int adminId, + int categoryId, int? customisationId ); } @@ -19,28 +18,23 @@ public class CourseDelegatesService : ICourseDelegatesService { private readonly ICourseDataService courseDataService; private readonly ICourseDelegatesDataService courseDelegatesDataService; - private readonly IUserDataService userDataService; public CourseDelegatesService( ICourseDataService courseDataService, - IUserDataService userDataService, ICourseDelegatesDataService courseDelegatesDataService ) { this.courseDataService = courseDataService; - this.userDataService = userDataService; this.courseDelegatesDataService = courseDelegatesDataService; } public CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( int centreId, - int adminId, + int categoryId, int? customisationId ) { - var adminUser = userDataService.GetAdminUserById(adminId)!; - - var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, adminUser.CategoryId).ToList(); + var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId).ToList(); var currentCustomisationId = customisationId ?? courses.FirstOrDefault()?.CustomisationId; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs index 72f26822e0..c8a1e37019 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/CourseDelegatesController.cs @@ -23,10 +23,10 @@ ICourseDelegatesService courseDelegatesService public IActionResult Index(int? customisationId = null) { - var adminId = User.GetAdminId()!.Value; var centreId = User.GetCentreId(); + var categoryId = User.GetAdminCategoryId()!.Value; var courseDelegatesData = - courseDelegatesService.GetCoursesAndCourseDelegatesForCentre(centreId, adminId, customisationId); + courseDelegatesService.GetCoursesAndCourseDelegatesForCentre(centreId, categoryId, customisationId); var model = new CourseDelegatesViewModel(courseDelegatesData); diff --git a/DigitalLearningSolutions.Web/Helpers/DateHelper.cs b/DigitalLearningSolutions.Web/Helpers/DateHelper.cs new file mode 100644 index 0000000000..4bc2a0c11b --- /dev/null +++ b/DigitalLearningSolutions.Web/Helpers/DateHelper.cs @@ -0,0 +1,9 @@ +namespace DigitalLearningSolutions.Web.Helpers +{ + public static class DateHelper + { + public static string StandardDateFormat = "dd/MM/yyyy"; + + public static string StandardDateAndTimeFormat = "dd/MM/yyyy hh:mm"; + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs index d84a841392..9a031942b7 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs @@ -6,7 +6,6 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers.FilterOptions; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; - using FluentMigrator.Runner.Extensions; public static class FilterableTagHelper { diff --git a/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs index 09df0abb63..1145759d8b 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs @@ -1,6 +1,7 @@ namespace DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre { using System.Collections.Generic; + using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.ViewModels.Common; @@ -17,7 +18,7 @@ public SummaryViewModel(DelegateRegistrationByCentreData data) IsPasswordSet = data.IsPasswordSet; if (data.ShouldSendEmail) { - WelcomeEmailDate = data.WelcomeEmailDate!.Value.ToString("dd/MM/yyyy"); + WelcomeEmailDate = data.WelcomeEmailDate!.Value.ToString(DateHelper.StandardDateFormat); } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/SystemNotifications/UnacknowledgedNotificationViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/SystemNotifications/UnacknowledgedNotificationViewModel.cs index c7c1f7bceb..d7680add74 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/SystemNotifications/UnacknowledgedNotificationViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/SystemNotifications/UnacknowledgedNotificationViewModel.cs @@ -1,7 +1,7 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.SystemNotifications { - using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Web.Helpers; public class UnacknowledgedNotificationViewModel { diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/CourseDetails/CourseSummaryViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/CourseDetails/CourseSummaryViewModel.cs index 4d8a6beda1..2de055f352 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/CourseDetails/CourseSummaryViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/CourseDetails/CourseSummaryViewModel.cs @@ -1,14 +1,15 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup.CourseDetails { using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Web.Helpers; public class CourseSummaryViewModel { public CourseSummaryViewModel(CourseDetails courseDetails) { CurrentVersion = courseDetails.CurrentVersion; - CreatedDate = courseDetails.CreatedDate.ToString("dd/MM/yyyy"); - LastAccessed = courseDetails.LastAccessed?.ToString("dd/MM/yyyy"); + CreatedDate = courseDetails.CreatedDate.ToString(DateHelper.StandardDateFormat); + LastAccessed = courseDetails.LastAccessed?.ToString(DateHelper.StandardDateFormat); Completions = courseDetails.CompletedCount; ActiveLearners = courseDetails.InProgressCount; } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs index 1961c0c285..89787ea22b 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs @@ -6,8 +6,6 @@ public class SearchableCourseDelegateViewModel : BaseFilterableViewModel { - private const string DateFormat = "dd/MM/yyyy hh:mm"; - public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) { DelegateId = courseDelegate.DelegateId; @@ -16,10 +14,10 @@ public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) Active = courseDelegate.Active; ProgressId = courseDelegate.ProgressId; Locked = courseDelegate.Locked; - LastUpdated = courseDelegate.LastUpdated.ToString(DateFormat); - Enrolled = courseDelegate.Enrolled.ToString(DateFormat); - CompleteBy = courseDelegate.CompleteBy?.ToString(DateFormat); - RemovedDate = courseDelegate.RemovedDate?.ToString(DateFormat); + LastUpdated = courseDelegate.LastUpdated.ToString(DateHelper.StandardDateAndTimeFormat); + Enrolled = courseDelegate.Enrolled.ToString(DateHelper.StandardDateAndTimeFormat); + CompleteBy = courseDelegate.CompleteBy?.ToString(DateHelper.StandardDateAndTimeFormat); + RemovedDate = courseDelegate.RemovedDate?.ToString(DateHelper.StandardDateAndTimeFormat); Tags = FilterableTagHelper.GetCurrentTagsForCourseDelegate(courseDelegate); } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateCourseInfoViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateCourseInfoViewModel.cs index 220a2dc632..f48def7326 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateCourseInfoViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateCourseInfoViewModel.cs @@ -4,11 +4,10 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates using System.Collections.Generic; using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Models.CustomPrompts; + using DigitalLearningSolutions.Web.Helpers; public class DelegateCourseInfoViewModel { - private const string DateFormat = "dd/MM/yyyy"; - public DelegateCourseInfoViewModel(DelegateCourseDetails details) { var info = details.DelegateCourseInfo; @@ -17,11 +16,11 @@ public DelegateCourseInfoViewModel(DelegateCourseDetails details) CustomisationName = info.CustomisationName; SupervisorForename = info.SupervisorForename; SupervisorSurname = info.SupervisorSurname; - Enrolled = info.Enrolled.ToString(DateFormat); - LastUpdated = info.LastUpdated.ToString(DateFormat); - CompleteBy = info.CompleteBy?.ToString(DateFormat); - Completed = info.Completed?.ToString(DateFormat); - Evaluated = info.Evaluated?.ToString(DateFormat); + Enrolled = info.Enrolled.ToString(DateHelper.StandardDateFormat); + LastUpdated = info.LastUpdated.ToString(DateHelper.StandardDateFormat); + CompleteBy = info.CompleteBy?.ToString(DateHelper.StandardDateFormat); + Completed = info.Completed?.ToString(DateHelper.StandardDateFormat); + Evaluated = info.Evaluated?.ToString(DateHelper.StandardDateFormat); EnrolmentMethod = info.EnrolmentMethodId switch { 1 => "Self enrolled", diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateGroups/GroupCourseViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateGroups/GroupCourseViewModel.cs index 93160909ff..907c0dc21d 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateGroups/GroupCourseViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateGroups/GroupCourseViewModel.cs @@ -14,7 +14,7 @@ public GroupCourseViewModel(GroupCourse groupCourse) : null; IsMandatory = groupCourse.IsMandatory ? "Mandatory" : "Not mandatory"; IsAssessed = groupCourse.IsAssessed ? "Assessed" : "Not assessed"; - AddedToGroup = groupCourse.AddedToGroup.ToString("dd/MM/yyyy"); + AddedToGroup = groupCourse.AddedToGroup.ToString(DateHelper.StandardDateFormat); CompleteWithin = DisplayStringHelper.ConvertNumberToMonthsString(groupCourse.CompleteWithinMonths); ValidFor = DisplayStringHelper.ConvertNumberToMonthsString(groupCourse.ValidityMonths); } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateInfoViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateInfoViewModel.cs index fa2562cf15..c66d00b410 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateInfoViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateInfoViewModel.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common; public class DelegateInfoViewModel @@ -22,7 +23,7 @@ public DelegateInfoViewModel(DelegateUserCard delegateUser, List View learning log + @if (Model.Locked) { + + Unlock account + + }
From 82d686f9dea33f2c8c351e4d5ea00684e872a0a2 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Fri, 3 Sep 2021 15:32:23 +0100 Subject: [PATCH 07/12] HEEDLS-563 Review markups --- .../CourseDelegatesDataServiceTests.cs | 5 +++- .../Services/CourseDelegatesServiceTests.cs | 2 +- .../CourseDelegatesDataService.cs | 22 ++++++++++++-- .../Models/CourseDelegates/CourseDelegate.cs | 7 +++++ .../Models/Courses/Course.cs | 2 ++ .../Models/Courses/CourseDetails.cs | 14 ++------- .../Services/CourseDelegatesService.cs | 9 ++++-- .../CourseDelegateFilterOptions.cs | 17 +++++++++++ .../Helpers/FilterableTagHelper.cs | 9 ++++++ .../Styles/shared/cardWithThreeButtons.scss | 12 ++++++++ .../delegates/courseDelegates.scss | 1 + .../delegates/delegateGroups.scss | 12 +------- .../CourseDelegatesViewModel.cs | 20 +++++++++---- .../SearchableCourseDelegateViewModel.cs | 4 +++ .../CourseDelegates/SelectedCourseDetails.cs | 19 ++++++++++++ .../Delegates/CourseDelegates/Index.cshtml | 30 ++++++++++++++----- .../_SearchableCourseDelegateCard.cshtml | 24 +++++++++++---- 17 files changed, 162 insertions(+), 47 deletions(-) create mode 100644 DigitalLearningSolutions.Web/Styles/shared/cardWithThreeButtons.scss create mode 100644 DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SelectedCourseDetails.cs diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs index fc5d3e2e0d..b1794a7978 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDelegatesDataServiceTests.cs @@ -37,7 +37,10 @@ public void GetDelegatesOnCourse_returns_expected_values() LastUpdated = new DateTime(2012, 07, 31, 10, 18, 39, 993), Locked = false, ProgressId = 18395, - RemovedDate = null + RemovedDate = null, + Completed = null, + AllAttempts = 0, + AttemptsPassed = 0 }; // When diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs index 288fc58193..c2ae917593 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs @@ -30,7 +30,7 @@ public void Setup() } [Test] - public void GetCoursesAndCourseDelegatesForCentre_expected_values() + public void GetCoursesAndCourseDelegatesForCentre_populates_course_delegates_data() { // Given const int centreId = 2; diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs index c208240b50..7f9127c9c0 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDelegatesDataService.cs @@ -14,6 +14,20 @@ public class CourseDelegatesDataService : ICourseDelegatesDataService { private readonly IDbConnection connection; + private const string AllAttemptsQuery = + @"(SELECT COUNT(aa.AssessAttemptID) + FROM dbo.AssessAttempts AS aa + INNER JOIN dbo.Candidates AS can ON can.CandidateID = aa.CandidateID + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] IS NOT NULL + AND can.CentreID = @centreId) AS AllAttempts"; + + private const string AttemptsPassedQuery = + @"(SELECT COUNT(aa.AssessAttemptID) + FROM dbo.AssessAttempts AS aa + INNER JOIN dbo.Candidates AS can ON can.CandidateID = aa.CandidateID + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] = 1 + AND can.CentreID = @centreId) AS AttemptsPassed"; + public CourseDelegatesDataService(IDbConnection connection) { this.connection = connection; @@ -22,7 +36,7 @@ public CourseDelegatesDataService(IDbConnection connection) public IEnumerable GetDelegatesOnCourse(int customisationId, int centreId) { return connection.Query( - @"SELECT + $@"SELECT c.CandidateID AS DelegateId, c.CandidateNumber, c.FirstName, @@ -34,9 +48,13 @@ public IEnumerable GetDelegatesOnCourse(int customisationId, int p.SubmittedTime AS LastUpdated, c.DateRegistered AS Enrolled, p.CompleteByDate AS CompleteBy, - p.RemovedDate + p.RemovedDate, + p.Completed, + {AllAttemptsQuery}, + {AttemptsPassedQuery} FROM Candidates AS c INNER JOIN Progress AS p ON p.CandidateID = c.CandidateID + INNER JOIN Customisations cu ON cu.CustomisationID = p.CustomisationID WHERE c.CentreID = @centreId AND p.CustomisationID = @customisationId", new { customisationId, centreId } diff --git a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs index e177616169..d83a54a865 100644 --- a/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs +++ b/DigitalLearningSolutions.Data/Models/CourseDelegates/CourseDelegate.cs @@ -16,9 +16,16 @@ public class CourseDelegate public DateTime Enrolled { get; set; } public DateTime? CompleteBy { get; set; } public DateTime? RemovedDate { get; set; } + public DateTime? Completed { get; set; } + public int AllAttempts { get; set; } + public int AttemptsPassed { get; set; } public string FullName => (string.IsNullOrEmpty(FirstName) ? "" : $"{FirstName} ") + LastName; public string TitleName => FullName + (string.IsNullOrEmpty(EmailAddress) ? "" : $" ({EmailAddress})"); + + public bool Removed => RemovedDate.HasValue; + + public double PassRate => AllAttempts == 0 ? 0 : Math.Round(100 * AttemptsPassed / (double)AllAttempts); } } diff --git a/DigitalLearningSolutions.Data/Models/Courses/Course.cs b/DigitalLearningSolutions.Data/Models/Courses/Course.cs index 65e844edce..88a5467543 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/Course.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/Course.cs @@ -13,6 +13,8 @@ public class Course : BaseSearchableItem ? ApplicationName : ApplicationName + " - " + CustomisationName; + public string CourseNameWithInactiveFlag => !Active ? "Inactive - " + CourseName : CourseName; + public override string SearchableName { get => SearchableNameOverrideForFuzzySharp ?? CourseName; diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseDetails.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseDetails.cs index b84b30d20f..8b6e6b9ed2 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/CourseDetails.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseDetails.cs @@ -2,13 +2,8 @@ { using System; - public class CourseDetails + public class CourseDetails : Course { - public int CustomisationId { get; set; } - public int CentreId { get; set; } - public int ApplicationId { get; set; } - public string ApplicationName { get; set; } - public string CustomisationName { get; set; } public int CurrentVersion { get; set; } public DateTime CreatedDate { get; set; } public DateTime? LastAccessed { get; set; } @@ -22,7 +17,6 @@ public class CourseDetails public bool SelfRegister { get; set; } public bool DiagObjSelect { get; set; } public bool HideInLearnerPortal { get; set; } - public bool Active { get; set; } public int DelegateCount { get; set; } public int CompletedCount { get; set; } public int CompleteWithinMonths { get; set; } @@ -36,11 +30,7 @@ public class CourseDetails public bool ApplyLpDefaultsToSelfEnrol { get; set; } public int InProgressCount => DelegateCount - CompletedCount; - - public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) - ? ApplicationName - : ApplicationName + " - " + CustomisationName; - + public string? RefreshToCourseName { get diff --git a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs index df4d0ab1df..03a39ba540 100644 --- a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs @@ -35,15 +35,20 @@ public CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( ) { var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId).ToList(); + var activeCoursesAlphabetical = courses.Where(c => c.Active).OrderBy(c => c.CourseName); + var inactiveCoursesAlphabetical = + courses.Where(c => !c.Active).OrderBy(c => c.CourseName); - var currentCustomisationId = customisationId ?? courses.FirstOrDefault()?.CustomisationId; + var orderedCourses = activeCoursesAlphabetical.Concat(inactiveCoursesAlphabetical).ToList(); + + var currentCustomisationId = customisationId ?? orderedCourses.FirstOrDefault()?.CustomisationId; var courseDelegates = currentCustomisationId.HasValue ? courseDelegatesDataService.GetDelegatesOnCourse(currentCustomisationId.Value, centreId) .ToList() : new List(); - return new CourseDelegatesData(currentCustomisationId, courses, courseDelegates); + return new CourseDelegatesData(currentCustomisationId, orderedCourses, courseDelegates); } } } diff --git a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs index e473d3e9fc..1045c0c5b0 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterOptions/CourseDelegateFilterOptions.cs @@ -37,4 +37,21 @@ public static class CourseDelegateProgressLockedFilterOptions FilterStatus.Default ); } + + public static class CourseDelegateProgressRemovedFilterOptions + { + private const string Group = "ProgressRemoved"; + + public static readonly FilterOptionViewModel Removed = new FilterOptionViewModel( + "Removed", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Removed) + FilteringHelper.Separator + "true", + FilterStatus.Warning + ); + + public static readonly FilterOptionViewModel NotRemoved = new FilterOptionViewModel( + "Not removed", + Group + FilteringHelper.Separator + nameof(CourseDelegate.Removed) + FilteringHelper.Separator + "false", + FilterStatus.Default + ); + } } diff --git a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs index 9a031942b7..9fb9a893e2 100644 --- a/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/FilterableTagHelper.cs @@ -104,6 +104,15 @@ public static IEnumerable GetCurrentTagsForCourseDelegat tags.Add(new SearchableTagViewModel(CourseDelegateProgressLockedFilterOptions.NotLocked, true)); } + if (courseDelegate.RemovedDate.HasValue) + { + tags.Add(new SearchableTagViewModel(CourseDelegateProgressRemovedFilterOptions.Removed)); + } + else + { + tags.Add(new SearchableTagViewModel(CourseDelegateProgressRemovedFilterOptions.NotRemoved, true)); + } + return tags; } } diff --git a/DigitalLearningSolutions.Web/Styles/shared/cardWithThreeButtons.scss b/DigitalLearningSolutions.Web/Styles/shared/cardWithThreeButtons.scss new file mode 100644 index 0000000000..a0df785b43 --- /dev/null +++ b/DigitalLearningSolutions.Web/Styles/shared/cardWithThreeButtons.scss @@ -0,0 +1,12 @@ +@import "~nhsuk-frontend/packages/core/all"; + +.nhsuk-button.expander-card__button { + margin-right: nhsuk-spacing(2); + margin-bottom: nhsuk-spacing(3); + margin-top: nhsuk-spacing(0); + + @include govuk-media-query($until: tablet) { + margin-top: nhsuk-spacing(2); + margin-bottom: nhsuk-spacing(2); + } +} diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss index 072ac65aa6..003b288541 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss @@ -1,5 +1,6 @@ @import '../../shared/searchableElements/searchableElements'; @import '../../shared/headingButtons'; +@import '../../shared/cardWithThreeButtons'; .course-dropdown { @extend .input-with-submit-button; diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/delegateGroups.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/delegateGroups.scss index edf09307d1..568c5198c2 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/delegateGroups.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/delegateGroups.scss @@ -1,13 +1,3 @@ @import '../../shared/searchableElements/searchableElements'; @import '../../shared/headingButtons'; - -.nhsuk-button.expander-card__button { - margin-right: nhsuk-spacing(2); - margin-bottom: nhsuk-spacing(3); - margin-top: nhsuk-spacing(0); - - @include govuk-media-query($until: tablet) { - margin-top: nhsuk-spacing(2); - margin-bottom: nhsuk-spacing(2); - } -} +@import '../../shared/cardWithThreeButtons'; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs index 66760ff4f4..a360ab162d 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/CourseDelegatesViewModel.cs @@ -11,19 +11,27 @@ public class CourseDelegatesViewModel public CourseDelegatesViewModel(CourseDelegatesData courseDelegatesData) { CustomisationId = courseDelegatesData.CustomisationId; - var courseOptions = courseDelegatesData.Courses.Select(c => (c.CustomisationId, c.CourseName)); - Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, CustomisationId); - Active = courseDelegatesData.Courses.SingleOrDefault(c => c.CustomisationId == CustomisationId)?.Active; + var courseOptions = courseDelegatesData.Courses + .Select(c => (c.CustomisationId, c.CourseNameWithInactiveFlag)); + Courses = SelectListHelper.MapOptionsToSelectListItems(courseOptions, courseDelegatesData.CustomisationId); // TODO: HEEDLS-564 - paginate properly instead of taking 10. - Delegates = courseDelegatesData.Delegates.Take(10).Select(cd => new SearchableCourseDelegateViewModel(cd)); + var delegates = courseDelegatesData.Delegates.Take(10) + .Select(cd => new SearchableCourseDelegateViewModel(cd)); + CourseDetails = courseDelegatesData.CustomisationId.HasValue + ? new SelectedCourseDetails( + courseDelegatesData.CustomisationId.Value, + courseDelegatesData.Courses, + delegates + ) + : null; } public int? CustomisationId { get; set; } + public IEnumerable Courses { get; set; } - public bool? Active { get; set; } - public IEnumerable Delegates { get; set; } + public SelectedCourseDetails? CourseDetails { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs index 89787ea22b..a6de5e19c6 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SearchableCourseDelegateViewModel.cs @@ -17,7 +17,9 @@ public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) LastUpdated = courseDelegate.LastUpdated.ToString(DateHelper.StandardDateAndTimeFormat); Enrolled = courseDelegate.Enrolled.ToString(DateHelper.StandardDateAndTimeFormat); CompleteBy = courseDelegate.CompleteBy?.ToString(DateHelper.StandardDateAndTimeFormat); + Completed = courseDelegate.Completed?.ToString(DateHelper.StandardDateAndTimeFormat); RemovedDate = courseDelegate.RemovedDate?.ToString(DateHelper.StandardDateAndTimeFormat); + PassRate = courseDelegate.PassRate; Tags = FilterableTagHelper.GetCurrentTagsForCourseDelegate(courseDelegate); } @@ -30,6 +32,8 @@ public SearchableCourseDelegateViewModel(CourseDelegate courseDelegate) public string LastUpdated { get; set; } public string Enrolled { get; set; } public string? CompleteBy { get; set; } + public string? Completed { get; set; } public string? RemovedDate { get; set; } + public double PassRate { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SelectedCourseDetails.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SelectedCourseDetails.cs new file mode 100644 index 0000000000..58deb91406 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/CourseDelegates/SelectedCourseDetails.cs @@ -0,0 +1,19 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.Models.Courses; + + public class SelectedCourseDetails + { + public SelectedCourseDetails(int customisationId, IEnumerable courses, IEnumerable delegates) + { + Active = courses.Single(c => c.CustomisationId == customisationId).Active; + Delegates = delegates; + } + + public bool Active { get; set; } + + public IEnumerable Delegates { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml index f3b12c1819..5a2501f64d 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -30,13 +30,24 @@

Course delegates

- @if (Model.Courses.Any()) { +
+
+

+ Choose a course to see delegates enrolled on it. If you'd like to export delegates on all courses, click "Export all" above. +

+
+
+ + @if (Model.Courses.Any() && Model.CourseDetails != null) {
@@ -44,7 +55,8 @@ + aria-label="Choose course"> + @@ -52,23 +64,26 @@
- @if (!Model.Active == true) { + @if (!Model.CourseDetails.Active) {
-

The currently selected course is inactive.

+
+ Information: +

The currently selected course is inactive

+
}
- @if (!Model.Delegates.Any()) { + @if (!Model.CourseDetails.Delegates.Any()) { } else {
- @foreach (var groupModel in Model.Delegates) { + @foreach (var groupModel in Model.CourseDetails.Delegates) { }
@@ -78,7 +93,8 @@ } else {
-

There are no courses within the category you can view.

+

There are no courses set up in your centre for the category you manage.

+
} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml index ef7335d5f3..b7b4f11212 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml @@ -43,23 +43,37 @@
+
+
+ Completed date +
+ +
Removed date
+
+
+ Pass rate +
+
+ @Model.PassRate% +
+
- + View progress - - View learning log + + Remove from course @if (Model.Locked) { - - Unlock account + + Unlock progress }
From 1c0ef80314b79ef08fc9d37112ce3897c5a635ad Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Fri, 3 Sep 2021 15:40:08 +0100 Subject: [PATCH 08/12] HEEDLS-563 Shuffle some DateHelper methods around --- .../Helpers/DateHelper.cs | 25 ------------------ .../Centre/Reports/ReportsController.cs | 1 - .../Helpers/DateHelper.cs | 26 +++++++++++++++++++ .../Centre/Reports/ReportsViewModel.cs | 4 +-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/DigitalLearningSolutions.Data/Helpers/DateHelper.cs b/DigitalLearningSolutions.Data/Helpers/DateHelper.cs index 835b779a61..ff30284847 100644 --- a/DigitalLearningSolutions.Data/Helpers/DateHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/DateHelper.cs @@ -7,7 +7,6 @@ public static class DateHelper { - public static string StandardDateFormat = "dd/MM/yyyy"; public static DateTime ReferenceDate => new DateTime(1905, 1, 1); public static IEnumerable GetPeriodsBetweenDates( @@ -111,30 +110,6 @@ DateTime endDate ); } - public static string GetFormatStringForGraphLabel(ReportInterval interval) - { - return interval switch - { - ReportInterval.Days => "d/M/y", - ReportInterval.Weeks => "wc d/M/y", - ReportInterval.Months => "MMM yyyy", - ReportInterval.Quarters => "yyyy q", - _ => "yyyy" - }; - } - - public static string GetFormatStringForDateInTable(ReportInterval interval) - { - return interval switch - { - ReportInterval.Days => "d/MM/yyyy", - ReportInterval.Weeks => "Week commencing d/MM/yyyy", - ReportInterval.Months => "MMMM, yyyy", - ReportInterval.Quarters => "Q, yyyy", - _ => "yyyy" - }; - } - private static int ConvertMonthToQuarter(int month) { return (month - 1) / 3 + 1; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Reports/ReportsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Reports/ReportsController.cs index 799d5a3663..6a76810082 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Reports/ReportsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Reports/ReportsController.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using DigitalLearningSolutions.Data.Enums; - using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.TrackingSystem; using DigitalLearningSolutions.Data.Services; using DigitalLearningSolutions.Web.Helpers; diff --git a/DigitalLearningSolutions.Web/Helpers/DateHelper.cs b/DigitalLearningSolutions.Web/Helpers/DateHelper.cs index 4bc2a0c11b..6e391eb873 100644 --- a/DigitalLearningSolutions.Web/Helpers/DateHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/DateHelper.cs @@ -1,9 +1,35 @@ namespace DigitalLearningSolutions.Web.Helpers { + using DigitalLearningSolutions.Data.Enums; + public static class DateHelper { public static string StandardDateFormat = "dd/MM/yyyy"; public static string StandardDateAndTimeFormat = "dd/MM/yyyy hh:mm"; + + public static string GetFormatStringForGraphLabel(ReportInterval interval) + { + return interval switch + { + ReportInterval.Days => "d/M/y", + ReportInterval.Weeks => "wc d/M/y", + ReportInterval.Months => "MMM yyyy", + ReportInterval.Quarters => "yyyy q", + _ => "yyyy" + }; + } + + public static string GetFormatStringForDateInTable(ReportInterval interval) + { + return interval switch + { + ReportInterval.Days => "d/MM/yyyy", + ReportInterval.Weeks => "Week commencing d/MM/yyyy", + ReportInterval.Months => "MMMM, yyyy", + ReportInterval.Quarters => "Q, yyyy", + _ => "yyyy" + }; + } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Reports/ReportsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Reports/ReportsViewModel.cs index 5a8aaa3dac..c6930b3128 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Reports/ReportsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Reports/ReportsViewModel.cs @@ -1,9 +1,9 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports { + using DigitalLearningSolutions.Data.Models.TrackingSystem; + using DigitalLearningSolutions.Web.Helpers; using System.Collections.Generic; using System.Linq; - using DigitalLearningSolutions.Data.Helpers; - using DigitalLearningSolutions.Data.Models.TrackingSystem; public class ReportsViewModel { From d757924f1d7e07f7487aeb51631c1e484e7ac27a Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Fri, 3 Sep 2021 16:12:43 +0100 Subject: [PATCH 09/12] HEEDLS-563 Fix header button layout --- .../TrackingSystem/Delegates/CourseDelegates/Index.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml index 5a2501f64d..0802a1b20a 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/Index.cshtml @@ -26,15 +26,15 @@
-
+

Course delegates

- From c54befae8f492d4ac75054591509f69efe0063f7 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Mon, 6 Sep 2021 09:29:04 +0100 Subject: [PATCH 10/12] HEEDLS-563 Reorder buttons on card --- .../CourseDelegates/_SearchableCourseDelegateCard.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml index b7b4f11212..e86c5eb04e 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/CourseDelegates/_SearchableCourseDelegateCard.cshtml @@ -68,14 +68,14 @@ View progress - - Remove from course - @if (Model.Locked) { Unlock progress } + + Remove from course +
From 2da8b2bbdd1661efde0bdc0421a5e54a401c08c2 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Mon, 6 Sep 2021 15:38:51 +0100 Subject: [PATCH 11/12] HEEDLS-563 Rename data service methods and fix link --- .../DataServices/CourseDataServiceTests.cs | 12 ++++++------ .../Services/CourseDelegatesServiceTests.cs | 6 +++--- .../Services/CourseServiceTests.cs | 2 +- .../DataServices/CourseDataService.cs | 18 ++++++++++++------ .../Services/CourseDelegatesService.cs | 2 +- .../Services/CourseService.cs | 4 ++-- .../CourseContentControllerTests.cs | 4 ++-- .../CourseSetup/ManageCourseControllerTests.cs | 4 ++-- .../CourseSetup/CourseContentController.cs | 2 +- .../CourseSetup/ManageCourseController.cs | 2 +- .../delegates/courseDelegates.scss | 1 + .../Centre/TopCourses/Index.cshtml | 2 +- 12 files changed, 33 insertions(+), 26 deletions(-) diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index b7197db1ba..072ece846e 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -219,14 +219,14 @@ public void GetNumberOfActiveCoursesAtCentre_with_filtered_category_returns_expe } [Test] - public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statistics_correctly() + public void GetCourseStatisticsAtCentreForAdminCategoryId_should_return_course_statistics_correctly() { // Given const int centreId = 101; const int categoryId = 0; // When - var result = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId).ToList(); + var result = courseDataService.GetCourseStatisticsAtCentreForAdminCategoryId(centreId, categoryId).ToList(); // Then var expectedFirstCourse = new CourseStatistics @@ -253,7 +253,7 @@ public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statis } [Test] - public void GetCourseDetailsByIdAtCentreForCategoryId_should_return_course_details_correctly() + public void GetCourseDetailsForAdminCategoryId_should_return_course_details_correctly() { // Given const int customisationId = 100; @@ -268,7 +268,7 @@ public void GetCourseDetailsByIdAtCentreForCategoryId_should_return_course_detai // When var result = - courseDataService.GetCourseDetails(customisationId, centreId, categoryId)!; + courseDataService.GetCourseDetailsForAdminCategoryId(customisationId, centreId, categoryId)!; // Overwrite the created time as it is populated by a default constraint and not consistent over different databases result.CreatedDate = fixedCreationDateTime; @@ -320,7 +320,7 @@ public void GetDelegateCoursesAttemptStats_should_return_delegate_course_info_co } [Test] - public void GetCoursesAtCentreForCategoryId_returns_expected_values() + public void GetCoursesAtCentreForAdminCategoryId_returns_expected_values() { // Given var expectedFirstCourse = new Course @@ -334,7 +334,7 @@ public void GetCoursesAtCentreForCategoryId_returns_expected_values() }; // When - var result = courseDataService.GetCoursesAtCentreForCategoryId(2, 0).ToList(); + var result = courseDataService.GetCoursesAtCentreForAdminCategoryId(2, 0).ToList(); // Then using (new AssertionScope()) diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs index c2ae917593..8cfb9e0bec 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseDelegatesServiceTests.cs @@ -35,7 +35,7 @@ public void GetCoursesAndCourseDelegatesForCentre_populates_course_delegates_dat // Given const int centreId = 2; const int categoryId = 1; - A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId)) + A.CallTo(() => courseDataService.GetCoursesAtCentreForAdminCategoryId(centreId, categoryId)) .Returns(new List { new Course { CustomisationId = 1 } }); A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) .Returns(new List { new CourseDelegate() }); @@ -60,7 +60,7 @@ public void GetCoursesAndCourseDelegatesForCentre_populates_course_delegates_dat public void GetCoursesAndCourseDelegatesForCentre_contains_empty_lists_with_no_courses_in_category() { // Given - A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(2, 7)).Returns(new List()); + A.CallTo(() => courseDataService.GetCoursesAtCentreForAdminCategoryId(2, 7)).Returns(new List()); // When var result = courseDelegatesService.GetCoursesAndCourseDelegatesForCentre(2, 7, null); @@ -83,7 +83,7 @@ public void GetCoursesAndCourseDelegatesForCentre_uses_passed_in_customisation_i const int customisationId = 2; const int centreId = 2; const int categoryId = 1; - A.CallTo(() => courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId)) + A.CallTo(() => courseDataService.GetCoursesAtCentreForAdminCategoryId(centreId, categoryId)) .Returns(new List { new Course { CustomisationId = 1 } }); A.CallTo(() => courseDelegatesDataService.GetDelegatesOnCourse(A._, A._)) .Returns(new List { new CourseDelegate() }); diff --git a/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs index d3f37a0385..a2f8a38d5e 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs @@ -21,7 +21,7 @@ public class CourseServiceTests public void Setup() { courseDataService = A.Fake(); - A.CallTo(() => courseDataService.GetCourseStatisticsAtCentreForCategoryId(CentreId, AdminCategoryId)) + A.CallTo(() => courseDataService.GetCourseStatisticsAtCentreForAdminCategoryId(CentreId, AdminCategoryId)) .Returns(GetSampleCourses()); courseAdminFieldsService = A.Fake(); courseService = new CourseService(courseDataService, courseAdminFieldsService); diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index d799046217..8c5f6619a3 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -17,11 +17,11 @@ public interface ICourseDataService void RemoveCurrentCourse(int progressId, int candidateId); void EnrolOnSelfAssessment(int selfAssessmentId, int candidateId); int GetNumberOfActiveCoursesAtCentreForCategory(int centreId, int categoryId); - IEnumerable GetCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId); + IEnumerable GetCourseStatisticsAtCentreForAdminCategoryId(int centreId, int categoryId); IEnumerable GetDelegateCoursesInfo(int delegateId); (int totalAttempts, int attemptsPassed) GetDelegateCourseAttemptStats(int delegateId, int customisationId); - CourseDetails? GetCourseDetails(int customisationId, int centreId, int categoryId); - IEnumerable GetCoursesAtCentreForCategoryId(int centreId, int categoryId); + CourseDetails? GetCourseDetailsForAdminCategoryId(int customisationId, int centreId, int categoryId); + IEnumerable GetCoursesAtCentreForAdminCategoryId(int centreId, int categoryId); } public class CourseDataService : ICourseDataService @@ -172,7 +172,9 @@ FROM Customisations AS c ); } - public IEnumerable GetCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId) + // Admins have a non-nullable category ID where 0 = all. This is why we have the + // @categoryId = 0 in the WHERE clause, to prevent filtering on category ID when it is 0 + public IEnumerable GetCourseStatisticsAtCentreForAdminCategoryId(int centreId, int categoryId) { return connection.Query( @$"SELECT @@ -254,7 +256,9 @@ FROM AssessAttempts aa ); } - public CourseDetails? GetCourseDetails(int customisationId, int centreId, int categoryId) + // Admins have a non-nullable category ID where 0 = all. This is why we have the + // @categoryId = 0 in the WHERE clause, to prevent filtering on category ID when it is 0 + public CourseDetails? GetCourseDetailsForAdminCategoryId(int customisationId, int centreId, int categoryId) { return connection.Query( @$"SELECT @@ -301,7 +305,9 @@ AND ap.ArchivedDate IS NULL ).FirstOrDefault(); } - public IEnumerable GetCoursesAtCentreForCategoryId(int centreId, int categoryId) + // Admins have a non-nullable category ID where 0 = all. This is why we have the + // @categoryId = 0 in the WHERE clause, to prevent filtering on category ID when it is 0 + public IEnumerable GetCoursesAtCentreForAdminCategoryId(int centreId, int categoryId) { return connection.Query( @"SELECT diff --git a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs index 03a39ba540..175dbfe492 100644 --- a/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseDelegatesService.cs @@ -34,7 +34,7 @@ public CourseDelegatesData GetCoursesAndCourseDelegatesForCentre( int? customisationId ) { - var courses = courseDataService.GetCoursesAtCentreForCategoryId(centreId, categoryId).ToList(); + var courses = courseDataService.GetCoursesAtCentreForAdminCategoryId(centreId, categoryId).ToList(); var activeCoursesAlphabetical = courses.Where(c => c.Active).OrderBy(c => c.CourseName); var inactiveCoursesAlphabetical = courses.Where(c => !c.Active).OrderBy(c => c.CourseName); diff --git a/DigitalLearningSolutions.Data/Services/CourseService.cs b/DigitalLearningSolutions.Data/Services/CourseService.cs index 14e052f53c..4a37781e7b 100644 --- a/DigitalLearningSolutions.Data/Services/CourseService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseService.cs @@ -25,13 +25,13 @@ public CourseService(ICourseDataService courseDataService, ICourseAdminFieldsSer public IEnumerable GetTopCourseStatistics(int centreId, int categoryId) { - var allCourses = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId); + var allCourses = courseDataService.GetCourseStatisticsAtCentreForAdminCategoryId(centreId, categoryId); return allCourses.Where(c => c.Active).OrderByDescending(c => c.InProgressCount); } public IEnumerable GetCentreSpecificCourseStatistics(int centreId, int categoryId) { - var allCourses = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId); + var allCourses = courseDataService.GetCourseStatisticsAtCentreForAdminCategoryId(centreId, categoryId); return allCourses.Where(c => c.CentreId == centreId); } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/CourseContentControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/CourseContentControllerTests.cs index 66337e2240..44e7a64beb 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/CourseContentControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/CourseContentControllerTests.cs @@ -30,7 +30,7 @@ public void Setup() public void Index_returns_NotFound_when_no_appropriate_course_found() { // Given - A.CallTo(() => courseDataService.GetCourseDetails(A._, A._, A._)).Returns(null); + A.CallTo(() => courseDataService.GetCourseDetailsForAdminCategoryId(A._, A._, A._)).Returns(null); // When var result = controller.Index(1); @@ -43,7 +43,7 @@ public void Index_returns_NotFound_when_no_appropriate_course_found() public void Index_returns_Index_page_when_appropriate_course_found() { // Given - A.CallTo(() => courseDataService.GetCourseDetails(A._, A._, A._)) + A.CallTo(() => courseDataService.GetCourseDetailsForAdminCategoryId(A._, A._, A._)) .Returns(CourseDetailsTestHelper.GetDefaultCourseDetails()); A.CallTo(() => sectionService.GetSectionsAndTutorialsForCustomisation(A._, A._)) .Returns(new List
()); diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/ManageCourseControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/ManageCourseControllerTests.cs index 162757bdd0..28e6382838 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/ManageCourseControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CourseSetup/ManageCourseControllerTests.cs @@ -26,7 +26,7 @@ public void Setup() public void Index_returns_NotFound_when_no_appropriate_course_found() { // Given - A.CallTo(() => courseDataService.GetCourseDetails(A._, A._, A._)) + A.CallTo(() => courseDataService.GetCourseDetailsForAdminCategoryId(A._, A._, A._)) .Returns(null); // When @@ -40,7 +40,7 @@ public void Index_returns_NotFound_when_no_appropriate_course_found() public void Index_returns_ManageCourse_page_when_appropriate_course_found() { // Given - A.CallTo(() => courseDataService.GetCourseDetails(A._, A._, A._)) + A.CallTo(() => courseDataService.GetCourseDetailsForAdminCategoryId(A._, A._, A._)) .Returns(new CourseDetails()); // When diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseContentController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseContentController.cs index 1c86722af2..5d27718091 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseContentController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseContentController.cs @@ -28,7 +28,7 @@ public IActionResult Index(int customisationId) { var centreId = User.GetCentreId(); var categoryId = User.GetAdminCategoryId()!; - var courseDetails = courseDataService.GetCourseDetails(customisationId, centreId, categoryId.Value); + var courseDetails = courseDataService.GetCourseDetailsForAdminCategoryId(customisationId, centreId, categoryId.Value); if (courseDetails == null) { diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/ManageCourseController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/ManageCourseController.cs index 8dea8b9842..d7dcf5139f 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/ManageCourseController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/ManageCourseController.cs @@ -26,7 +26,7 @@ public IActionResult Index(int customisationId) var centreId = User.GetCentreId(); var categoryId = User.GetAdminCategoryId()!; - var courseDetails = courseDataService.GetCourseDetails( + var courseDetails = courseDataService.GetCourseDetailsForAdminCategoryId( customisationId, centreId, categoryId.Value diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss index 003b288541..e4c20f6deb 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss @@ -2,6 +2,7 @@ @import '../../shared/headingButtons'; @import '../../shared/cardWithThreeButtons'; +// TODO Fix this to match the new styles for the filter components from 580 .course-dropdown { @extend .input-with-submit-button; width: 63%; diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml index e256f31f9d..9763a08c0a 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml @@ -72,7 +72,7 @@ - View more + View more
From 471fd1e203eb1aa0c8de9e63c87af00dd1f55348 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Mon, 6 Sep 2021 15:50:38 +0100 Subject: [PATCH 12/12] HEEDLS-563 Fix error post merge --- .../Delegates/EmailDelegates/EmailDelegatesItemViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/EmailDelegates/EmailDelegatesItemViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/EmailDelegates/EmailDelegatesItemViewModel.cs index d82763cf69..fad9b31785 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/EmailDelegates/EmailDelegatesItemViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/EmailDelegates/EmailDelegatesItemViewModel.cs @@ -1,6 +1,6 @@ namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.EmailDelegates { - using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Data.Models.User; public class EmailDelegatesItemViewModel