From 344c619a6f32174fe0ed0b73aec4a799aeea56f9 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Thu, 1 Jul 2021 11:12:19 +0100 Subject: [PATCH 1/5] HEEDLS-470 - Implemented Top Courses page --- .../DataServices/CourseDataServiceTests.cs | 35 +++++++- .../Models/CourseStatisticTests.cs | 52 ++++++++++++ .../DataServices/CourseDataService.cs | 34 +++++++- .../Helpers/CourseHelper.cs | 25 ++++++ .../Models/Courses/CourseStatistics.cs | 24 ++++++ .../Services/CourseService.cs | 28 +++++++ .../BasicAccessibilityTests.cs | 1 + .../Centre/TopCourses/TopCoursesController.cs | 35 ++++++++ .../Helpers/CustomClaimHelper.cs | 5 ++ DigitalLearningSolutions.Web/Startup.cs | 3 +- .../Centre/TopCourses/TopCoursesViewModel.cs | 14 ++++ .../_DashboardBottomCardGroup.cshtml | 2 +- .../Centre/TopCourses/Index.cshtml | 80 +++++++++++++++++++ 13 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs create mode 100644 DigitalLearningSolutions.Data/Helpers/CourseHelper.cs create mode 100644 DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs create mode 100644 DigitalLearningSolutions.Data/Services/CourseService.cs create mode 100644 DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs create mode 100644 DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/TopCourses/TopCoursesViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index e54b5eaf94..0a28abb817 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -14,7 +14,7 @@ namespace DigitalLearningSolutions.Data.Tests.DataServices public class CourseDataServiceTests { - private CourseDataDataService courseDataService; + private CourseDataService courseDataService = null!; [OneTimeSetUp] public void OneTimeSetUp() @@ -26,8 +26,8 @@ public void OneTimeSetUp() public void Setup() { var connection = ServiceTestHelper.GetDatabaseConnection(); - var logger = A.Fake>(); - courseDataService = new CourseDataDataService(connection, logger); + var logger = A.Fake>(); + courseDataService = new CourseDataService(connection, logger); } [Test] @@ -216,5 +216,34 @@ public void GetNumberOfActiveCoursesAtCentre_with_filtered_category_returns_expe // Then count.Should().Be(3); } + + [Test] + public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statistics_correctly() + { + // When + const int centreId = 101; + const int categoryId = 0; + var result = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId).ToList(); + + // Then + var expectedFirstCourse = new CourseStatistics + { + CustomisationId = 100, + CentreId = 101, + Active = false, + AllCentres = false, + AspMenu = false, + ArchivedDate = null, + ApplicationName = "Entry Level - Win XP, Office 2003/07 OLD", + CustomisationName = "Standard", + DelegateCount = 25, + AllAttempts = 49, + AttemptsPassed = 34, + CompletedCount = 5 + }; + + result.Should().HaveCount(267); + result.First().Should().BeEquivalentTo(expectedFirstCourse); + } } } diff --git a/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs new file mode 100644 index 0000000000..2124794773 --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs @@ -0,0 +1,52 @@ +namespace DigitalLearningSolutions.Data.Tests.Models +{ + using DigitalLearningSolutions.Data.Models.Courses; + using FluentAssertions; + using NUnit.Framework; + + public class CourseStatisticTests + { + [Test] + public void Pass_rate_should_be_calculated_from_attempts() + { + // When + var courseStatistics = new CourseStatistics + { + AllAttempts = 10, + AttemptsPassed = 5 + }; + + // Then + courseStatistics.PassRate.Should().Be(50); + } + + [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/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 29468946d1..8693ed8247 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; using Dapper; + using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Courses; using Microsoft.Extensions.Logging; @@ -16,14 +17,15 @@ 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); } - public class CourseDataDataService : ICourseDataService + public class CourseDataService : ICourseDataService { private readonly IDbConnection connection; - private readonly ILogger logger; + private readonly ILogger logger; - public CourseDataDataService(IDbConnection connection, ILogger logger) + public CourseDataService(IDbConnection connection, ILogger logger) { this.connection = connection; this.logger = logger; @@ -127,5 +129,31 @@ FROM Customisations AS c new { centreId, adminCategoryId } ); } + + public IEnumerable GetCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId) + { + return connection.Query( + @$"SELECT + cu.CustomisationID, + cu.CentreID, + cu.Active, + cu.AllCentres, + ap.ASPMenu, + ap.ArchivedDate, + ap.ApplicationName, + cu.CustomisationName, + {CourseHelper.DelegateCount}, + {CourseHelper.CompletedCount}, + {CourseHelper.AllAttempts}, + {CourseHelper.AttemptsPassed} + FROM dbo.Customisations AS cu + INNER JOIN dbo.CentreApplications AS ca ON ca.ApplicationID = cu.ApplicationID + INNER JOIN dbo.Applications AS ap ON ap.ApplicationID = ca.ApplicationID + WHERE (ap.CourseCategoryID = @categoryId OR @categoryId = 0) + AND (cu.CentreID = @centreId OR (cu.AllCentres = 1 AND ca.Active = 1)) + AND ca.CentreID = @centreId", + new { centreId, categoryId } + ); + } } } diff --git a/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs b/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs new file mode 100644 index 0000000000..73a91c04fd --- /dev/null +++ b/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs @@ -0,0 +1,25 @@ +namespace DigitalLearningSolutions.Data.Helpers +{ + public class CourseHelper + { + public const string DelegateCount = + @"(SELECT COUNT(CandidateID) + FROM dbo.Progress AS pr + WHERE pr.CustomisationID = cu.CustomisationID) AS DelegateCount"; + + public const string CompletedCount = + @"(SELECT COUNT(CandidateID) + FROM dbo.Progress AS pr + WHERE pr.CustomisationID = cu.CustomisationID AND pr.Completed IS NOT NULL) AS CompletedCount"; + + public const string AllAttempts = + @"(SELECT COUNT(AssessAttemptID) + FROM dbo.AssessAttempts AS aa + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] IS NOT NULL) AS AllAttempts"; + + public const string AttemptsPassed = + @"(SELECT COUNT(AssessAttemptID) + FROM dbo.AssessAttempts AS aa + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] = 1) AS AttemptsPassed"; + } +} diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs new file mode 100644 index 0000000000..2990ba779a --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs @@ -0,0 +1,24 @@ +namespace DigitalLearningSolutions.Data.Models.Courses +{ + using System; + + public class CourseStatistics + { + public int CustomisationId { get; set; } + public int CentreId { get; set; } + public bool Active { get; set; } + public bool AllCentres { get; set; } + public bool AspMenu { get; set; } + public DateTime? ArchivedDate { get; set; } + public string ApplicationName { get; set; } + public string? CustomisationName { get; set; } + public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) + ? ApplicationName + : ApplicationName + " - " + CustomisationName; + public int DelegateCount { get; set; } + public int CompletedCount { get; set; } + public int AllAttempts { get; set; } + public int AttemptsPassed { get; set; } + public double PassRate => AllAttempts == 0 ? 0 : 100 * AttemptsPassed / (double)AllAttempts; + } +} diff --git a/DigitalLearningSolutions.Data/Services/CourseService.cs b/DigitalLearningSolutions.Data/Services/CourseService.cs new file mode 100644 index 0000000000..d4dc1914a2 --- /dev/null +++ b/DigitalLearningSolutions.Data/Services/CourseService.cs @@ -0,0 +1,28 @@ +namespace DigitalLearningSolutions.Data.Services +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Models.Courses; + + public interface ICourseService + { + IEnumerable GetTopCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId); + } + + public class CourseService : ICourseService + { + private readonly ICourseDataService courseDataService; + + public CourseService(ICourseDataService courseDataService) + { + this.courseDataService = courseDataService; + } + + public IEnumerable GetTopCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId) + { + var allCourses = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId); + return allCourses.Where(c => c.Active).OrderByDescending(c => c.DelegateCount); + } + } +} diff --git a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs index d7f3235836..056dccd61c 100644 --- a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs +++ b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs @@ -36,6 +36,7 @@ public void Page_has_no_accessibility_errors(string url, string pageTitle) [InlineData("/TrackingSystem/CentreConfiguration/RegistrationPrompts", "Manage delegate registration prompts")] [InlineData("/TrackingSystem/CentreConfiguration/RegistrationPrompts/1/Remove", "Remove delegate registration prompt")] [InlineData("/TrackingSystem/Centre/Reports", "Centre reports")] + [InlineData("/TrackingSystem/Centre/TopCourses", "Top courses")] [InlineData("/TrackingSystem/Delegates/Approve", "Approve delegate registrations")] [InlineData("/NotificationPreferences", "Notification preferences")] [InlineData("/NotificationPreferences/Edit/AdminUser", "Update notification preferences")] diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs new file mode 100644 index 0000000000..9c271d729e --- /dev/null +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs @@ -0,0 +1,35 @@ +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre.TopCourses +{ + using System.Linq; + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.TopCourses; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + [Authorize(Policy = CustomPolicies.UserCentreAdmin)] + [Route("/TrackingSystem/Centre/TopCourses")] + public class TopCoursesController : Controller + { + private readonly ICourseService courseService; + private const int TopNumberOfCourses = 10; + + public TopCoursesController(ICourseService courseService) + { + this.courseService = courseService; + } + + public IActionResult Index() + { + var centreId = User.GetCentreId(); + var adminCategoryId = User.GetAdminCategoryId()!; + + var topCourses = + courseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(TopNumberOfCourses); + + var model = new TopCoursesViewModel(topCourses); + + return View(model); + } + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs b/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs index c8cce9ebeb..ce0f17b0d6 100644 --- a/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs @@ -25,6 +25,11 @@ public static int GetCentreId(this ClaimsPrincipal user) return user.GetCustomClaimAsRequiredInt(CustomClaimTypes.UserCentreId); } + public static int? GetAdminCategoryId(this ClaimsPrincipal user) + { + return user.GetCustomClaimAsInt(CustomClaimTypes.AdminCategoryId); + } + public static string? GetCustomClaim(this ClaimsPrincipal user, string customClaimType) { return user.FindFirst(customClaimType)?.Value; diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index 0083684763..90e084943b 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -133,7 +133,8 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/TopCourses/TopCoursesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/TopCourses/TopCoursesViewModel.cs new file mode 100644 index 0000000000..d2caf7cbcf --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/TopCourses/TopCoursesViewModel.cs @@ -0,0 +1,14 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.TopCourses +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.Models.Courses; + + public class TopCoursesViewModel + { + public TopCoursesViewModel(IEnumerable topCourses) + { + TopCourses = topCourses; + } + public IEnumerable TopCourses { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml index 6afc369f0b..037650d89a 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml @@ -28,7 +28,7 @@

- Top courses + Top courses

See information about your top courses

diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml new file mode 100644 index 0000000000..a5a34c9652 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml @@ -0,0 +1,80 @@ +@inject IConfiguration configuration + +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.TopCourses +@using Microsoft.Extensions.Configuration +@model TopCoursesViewModel + + +@{ + ViewData["Title"] = "Top courses"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +@section NavBreadcrumbs { + +} + +
+
+

Top courses

+ + + + + + + + + + + + + + @foreach (var course in Model.TopCourses) { + + + + + + + + + } + +
+ Name + + Delegates + + In progress + + Completed + + Pass rate + + Action +
+ Name @course.CourseName + + Delegates @course.DelegateCount + + In progress @(course.DelegateCount - course.CompletedCount) + + Completed @course.CompletedCount + + Pass rate @Math.Round(course.PassRate) % + + Action + View course +
+ + View more + +
+
From ac513e1bbab8d1915e1b578859fda569abdc9593 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Fri, 2 Jul 2021 11:52:48 +0100 Subject: [PATCH 2/5] HEEDLS-470 - review markups --- .../Models/CourseStatisticTests.cs | 22 +++++++++++++++---- .../Models/Courses/CourseStatistics.cs | 3 ++- .../Services/CourseService.cs | 2 +- .../TopCoursesController.cs | 10 ++++----- .../Centre/TopCourses/Index.cshtml | 22 +++++++++---------- 5 files changed, 37 insertions(+), 22 deletions(-) rename DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/{TopCourses => Dashboard}/TopCoursesController.cs (72%) diff --git a/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs index 2124794773..f659460cb7 100644 --- a/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Models/CourseStatisticTests.cs @@ -7,17 +7,31 @@ public class CourseStatisticTests { [Test] - public void Pass_rate_should_be_calculated_from_attempts() + public void Pass_rate_should_be_calculated_from_attempts_and_rounded() { // When var courseStatistics = new CourseStatistics { - AllAttempts = 10, - AttemptsPassed = 5 + AllAttempts = 1000, + AttemptsPassed = 514 }; // Then - courseStatistics.PassRate.Should().Be(50); + courseStatistics.PassRate.Should().Be(51); + } + + [Test] + public void InProgressCount_should_be_calculated_from_total_delegates_and_total_completed() + { + // When + var courseStatistics = new CourseStatistics + { + DelegateCount = 90, + CompletedCount = 48 + }; + + // Then + courseStatistics.InProgressCount.Should().Be(42); } [Test] diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs index 2990ba779a..ec98b45ac8 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs @@ -17,8 +17,9 @@ public class CourseStatistics : ApplicationName + " - " + CustomisationName; public int DelegateCount { get; set; } public int CompletedCount { get; set; } + public int InProgressCount => DelegateCount - CompletedCount; public int AllAttempts { get; set; } public int AttemptsPassed { get; set; } - public double PassRate => AllAttempts == 0 ? 0 : 100 * AttemptsPassed / (double)AllAttempts; + public double PassRate => AllAttempts == 0 ? 0 : Math.Round(100 * AttemptsPassed / (double)AllAttempts); } } diff --git a/DigitalLearningSolutions.Data/Services/CourseService.cs b/DigitalLearningSolutions.Data/Services/CourseService.cs index d4dc1914a2..e87b4269db 100644 --- a/DigitalLearningSolutions.Data/Services/CourseService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseService.cs @@ -22,7 +22,7 @@ public CourseService(ICourseDataService courseDataService) public IEnumerable GetTopCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId) { var allCourses = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId); - return allCourses.Where(c => c.Active).OrderByDescending(c => c.DelegateCount); + return allCourses.Where(c => c.Active).OrderByDescending(c => c.InProgressCount); } } } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs similarity index 72% rename from DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs rename to DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs index 9c271d729e..eadf29e779 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/TopCourses/TopCoursesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs @@ -1,4 +1,4 @@ -namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre.TopCourses +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre.Dashboard { using System.Linq; using DigitalLearningSolutions.Data.Services; @@ -11,12 +11,12 @@ [Route("/TrackingSystem/Centre/TopCourses")] public class TopCoursesController : Controller { - private readonly ICourseService courseService; - private const int TopNumberOfCourses = 10; + private readonly ICourseService CourseService; + private const int NumberOfTopCourses = 10; public TopCoursesController(ICourseService courseService) { - this.courseService = courseService; + CourseService = courseService; } public IActionResult Index() @@ -25,7 +25,7 @@ public IActionResult Index() var adminCategoryId = User.GetAdminCategoryId()!; var topCourses = - courseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(TopNumberOfCourses); + CourseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(NumberOfTopCourses); var model = new TopCoursesViewModel(topCourses); diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml index a5a34c9652..2b5b8e31ff 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml @@ -1,4 +1,4 @@ -@inject IConfiguration configuration +@inject IConfiguration Configuration @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.TopCourses @using Microsoft.Extensions.Configuration @@ -8,7 +8,7 @@ @{ ViewData["Title"] = "Top courses"; ViewData["Application"] = "Tracking System"; - ViewData["HeaderPath"] = $"{configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; } @@ -30,15 +30,15 @@ Name - - Delegates - In progress Completed + + Delegates + Pass rate @@ -54,27 +54,27 @@ Name @course.CourseName - Delegates @course.DelegateCount + In progress @course.InProgressCount - In progress @(course.DelegateCount - course.CompletedCount) + Completed @course.CompletedCount - Completed @course.CompletedCount + Delegates @course.DelegateCount - Pass rate @Math.Round(course.PassRate) % + Pass rate @course.PassRate % Action - View course + View course } - View more + View more
From 55aa95495e0dd029d2ba5261e48d2082cb8cd993 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Fri, 2 Jul 2021 13:49:31 +0100 Subject: [PATCH 3/5] HEEDLS-470 - change private field capitalisation to match other files. --- .../TrackingSystem/Centre/Dashboard/TopCoursesController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs index eadf29e779..1c14055a5f 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs @@ -11,12 +11,12 @@ [Route("/TrackingSystem/Centre/TopCourses")] public class TopCoursesController : Controller { - private readonly ICourseService CourseService; + private readonly ICourseService courseService; private const int NumberOfTopCourses = 10; public TopCoursesController(ICourseService courseService) { - CourseService = courseService; + this.courseService = courseService; } public IActionResult Index() @@ -25,7 +25,7 @@ public IActionResult Index() var adminCategoryId = User.GetAdminCategoryId()!; var topCourses = - CourseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(NumberOfTopCourses); + courseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(NumberOfTopCourses); var model = new TopCoursesViewModel(topCourses); From 71343ad25a0d5b308662efcd25f18072971843e2 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Tue, 6 Jul 2021 08:34:17 +0100 Subject: [PATCH 4/5] HEEDLS-470 - Minor review markups --- .../DataServices/CourseDataServiceTests.cs | 5 ++-- .../DataServices/CourseDataService.cs | 29 +++++++++++++++---- .../Helpers/CourseHelper.cs | 25 ---------------- .../Models/Courses/CourseStatistics.cs | 8 ++--- .../Services/CourseService.cs | 4 +-- .../Centre/Dashboard/TopCoursesController.cs | 2 +- .../Centre/TopCourses/Index.cshtml | 4 +-- 7 files changed, 35 insertions(+), 42 deletions(-) delete mode 100644 DigitalLearningSolutions.Data/Helpers/CourseHelper.cs diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs index 0a28abb817..0c7b57deca 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs @@ -220,9 +220,11 @@ public void GetNumberOfActiveCoursesAtCentre_with_filtered_category_returns_expe [Test] public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statistics_correctly() { - // When + // Given const int centreId = 101; const int categoryId = 0; + + // When var result = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId).ToList(); // Then @@ -232,7 +234,6 @@ public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statis CentreId = 101, Active = false, AllCentres = false, - AspMenu = false, ArchivedDate = null, ApplicationName = "Entry Level - Win XP, Office 2003/07 OLD", CustomisationName = "Standard", diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 8693ed8247..2493b0fc54 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Data; using Dapper; - using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Courses; using Microsoft.Extensions.Logging; @@ -22,6 +21,26 @@ public interface ICourseDataService public class CourseDataService : ICourseDataService { + private const string DelegateCountQuery = + @"(SELECT COUNT(CandidateID) + FROM dbo.Progress AS pr + WHERE pr.CustomisationID = cu.CustomisationID) AS DelegateCount"; + + private const string CompletedCountQuery = + @"(SELECT COUNT(CandidateID) + FROM dbo.Progress AS pr + WHERE pr.CustomisationID = cu.CustomisationID AND pr.Completed IS NOT NULL) AS CompletedCount"; + + private const string AllAttemptsQuery = + @"(SELECT COUNT(AssessAttemptID) + FROM dbo.AssessAttempts AS aa + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] IS NOT NULL) AS AllAttempts"; + + private const string AttemptsPassedQuery = + @"(SELECT COUNT(AssessAttemptID) + FROM dbo.AssessAttempts AS aa + WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] = 1) AS AttemptsPassed"; + private readonly IDbConnection connection; private readonly ILogger logger; @@ -142,10 +161,10 @@ public IEnumerable GetCourseStatisticsAtCentreForCategoryId(in ap.ArchivedDate, ap.ApplicationName, cu.CustomisationName, - {CourseHelper.DelegateCount}, - {CourseHelper.CompletedCount}, - {CourseHelper.AllAttempts}, - {CourseHelper.AttemptsPassed} + {DelegateCountQuery}, + {CompletedCountQuery}, + {AllAttemptsQuery}, + {AttemptsPassedQuery} FROM dbo.Customisations AS cu INNER JOIN dbo.CentreApplications AS ca ON ca.ApplicationID = cu.ApplicationID INNER JOIN dbo.Applications AS ap ON ap.ApplicationID = ca.ApplicationID diff --git a/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs b/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs deleted file mode 100644 index 73a91c04fd..0000000000 --- a/DigitalLearningSolutions.Data/Helpers/CourseHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace DigitalLearningSolutions.Data.Helpers -{ - public class CourseHelper - { - public const string DelegateCount = - @"(SELECT COUNT(CandidateID) - FROM dbo.Progress AS pr - WHERE pr.CustomisationID = cu.CustomisationID) AS DelegateCount"; - - public const string CompletedCount = - @"(SELECT COUNT(CandidateID) - FROM dbo.Progress AS pr - WHERE pr.CustomisationID = cu.CustomisationID AND pr.Completed IS NOT NULL) AS CompletedCount"; - - public const string AllAttempts = - @"(SELECT COUNT(AssessAttemptID) - FROM dbo.AssessAttempts AS aa - WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] IS NOT NULL) AS AllAttempts"; - - public const string AttemptsPassed = - @"(SELECT COUNT(AssessAttemptID) - FROM dbo.AssessAttempts AS aa - WHERE aa.CustomisationID = cu.CustomisationID AND aa.[Status] = 1) AS AttemptsPassed"; - } -} diff --git a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs index ec98b45ac8..d10b436c2a 100644 --- a/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs +++ b/DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs @@ -8,18 +8,18 @@ public class CourseStatistics public int CentreId { get; set; } public bool Active { get; set; } public bool AllCentres { get; set; } - public bool AspMenu { get; set; } public DateTime? ArchivedDate { get; set; } public string ApplicationName { get; set; } public string? CustomisationName { get; set; } - public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) - ? ApplicationName - : ApplicationName + " - " + CustomisationName; public int DelegateCount { get; set; } public int CompletedCount { get; set; } public int InProgressCount => DelegateCount - CompletedCount; public int AllAttempts { get; set; } public int AttemptsPassed { get; set; } + + public string CourseName => string.IsNullOrWhiteSpace(CustomisationName) + ? ApplicationName + : ApplicationName + " - " + CustomisationName; public double PassRate => AllAttempts == 0 ? 0 : Math.Round(100 * AttemptsPassed / (double)AllAttempts); } } diff --git a/DigitalLearningSolutions.Data/Services/CourseService.cs b/DigitalLearningSolutions.Data/Services/CourseService.cs index e87b4269db..acdb42ffbe 100644 --- a/DigitalLearningSolutions.Data/Services/CourseService.cs +++ b/DigitalLearningSolutions.Data/Services/CourseService.cs @@ -7,7 +7,7 @@ public interface ICourseService { - IEnumerable GetTopCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId); + IEnumerable GetTopCourseStatistics(int centreId, int categoryId); } public class CourseService : ICourseService @@ -19,7 +19,7 @@ public CourseService(ICourseDataService courseDataService) this.courseDataService = courseDataService; } - public IEnumerable GetTopCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId) + public IEnumerable GetTopCourseStatistics(int centreId, int categoryId) { var allCourses = courseDataService.GetCourseStatisticsAtCentreForCategoryId(centreId, categoryId); return allCourses.Where(c => c.Active).OrderByDescending(c => c.InProgressCount); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs index 1c14055a5f..ab3e3ad565 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/TopCoursesController.cs @@ -25,7 +25,7 @@ public IActionResult Index() var adminCategoryId = User.GetAdminCategoryId()!; var topCourses = - courseService.GetTopCourseStatisticsAtCentreForCategoryId(centreId, adminCategoryId.Value).Take(NumberOfTopCourses); + courseService.GetTopCourseStatistics(centreId, adminCategoryId.Value).Take(NumberOfTopCourses); var model = new TopCoursesViewModel(topCourses); diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml index 2b5b8e31ff..711f4180a4 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/TopCourses/Index.cshtml @@ -1,10 +1,8 @@ @inject IConfiguration Configuration - @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.TopCourses @using Microsoft.Extensions.Configuration @model TopCoursesViewModel - @{ ViewData["Title"] = "Top courses"; ViewData["Application"] = "Tracking System"; @@ -63,7 +61,7 @@ Delegates @course.DelegateCount - Pass rate @course.PassRate % + Pass rate @course.PassRate% Action From ca2ca265d683c14975d9321b688d4e1933534f67 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Tue, 6 Jul 2021 10:07:27 +0100 Subject: [PATCH 5/5] HEEDLS-470 - remove depreciated field from query --- DigitalLearningSolutions.Data/DataServices/CourseDataService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 2493b0fc54..46ceb01668 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -157,7 +157,6 @@ public IEnumerable GetCourseStatisticsAtCentreForCategoryId(in cu.CentreID, cu.Active, cu.AllCentres, - ap.ASPMenu, ap.ArchivedDate, ap.ApplicationName, cu.CustomisationName,