diff --git a/DigitalLearningSolutions.Data.Tests/Services/CentresServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/CentresServiceTests.cs new file mode 100644 index 0000000000..42a2cf381e --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/Services/CentresServiceTests.cs @@ -0,0 +1,73 @@ +namespace DigitalLearningSolutions.Data.Tests.Services +{ + using System; + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Data.Tests.TestHelpers; + using FakeItEasy; + using FluentAssertions; + using NUnit.Framework; + + public class CentresServiceTests + { + private ICentresDataService centresDataService = null!; + private IClockService clockService = null!; + private ICentresService centresService = null!; + + [SetUp] + public void Setup() + { + centresDataService = A.Fake(); + clockService = A.Fake(); + centresService = new CentresService(centresDataService, clockService); + + A.CallTo(() => clockService.UtcNow).Returns(new DateTime(2021, 1, 1)); + A.CallTo(() => centresDataService.GetCentreRanks(A._, A._, 10, A._)).Returns( + new[] + { + CentreTestHelper.GetCentreRank(1), + CentreTestHelper.GetCentreRank(2), + CentreTestHelper.GetCentreRank(3), + CentreTestHelper.GetCentreRank(4), + CentreTestHelper.GetCentreRank(5), + CentreTestHelper.GetCentreRank(6), + CentreTestHelper.GetCentreRank(7), + CentreTestHelper.GetCentreRank(8), + CentreTestHelper.GetCentreRank(9), + CentreTestHelper.GetCentreRank(10) + } + ); + } + + [Test] + public void GetCentreRanks_returns_expected_list() + { + // When + var result = centresService.GetCentresForCentreRankingPage(3, 14, null); + + // Then + result.Count().Should().Be(10); + } + + [Test] + public void GetCentreRankForCentre_returns_expected_value() + { + // When + var result = centresService.GetCentreRankForCentre(3); + + // Then + result.Should().Be(3); + } + + [Test] + public void GetCentreRankForCentre_returns_null_with_no_data_for_centre() + { + // When + var result = centresService.GetCentreRankForCentre(20); + + // Then + result.Should().BeNull(); + } + } +} diff --git a/DigitalLearningSolutions.Data.Tests/TestHelpers/CentreTestHelper.cs b/DigitalLearningSolutions.Data.Tests/TestHelpers/CentreTestHelper.cs index c21e63b92d..65f972fa50 100644 --- a/DigitalLearningSolutions.Data.Tests/TestHelpers/CentreTestHelper.cs +++ b/DigitalLearningSolutions.Data.Tests/TestHelpers/CentreTestHelper.cs @@ -1,6 +1,7 @@ namespace DigitalLearningSolutions.Data.Tests.TestHelpers { using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Data.Models.DbModels; public static class CentreTestHelper { @@ -70,5 +71,16 @@ public static Centre GetDefaultCentre ContractType = contractType }; } + + public static CentreRanking GetCentreRank(int rank) + { + return new CentreRanking + { + CentreId = rank, + Ranking = rank, + CentreName = $"Centre {rank}", + DelegateSessionCount = 10000-rank*10 + }; + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs index 403b3e5940..db60818a5b 100644 --- a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs @@ -5,6 +5,7 @@ using System.Data; using Dapper; using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Data.Models.DbModels; using Microsoft.Extensions.Logging; public interface ICentresDataService @@ -14,8 +15,7 @@ public interface ICentresDataService IEnumerable<(int, string)> GetActiveCentresAlphabetical(); Centre? GetCentreDetailsById(int centreId); - void UpdateCentreManagerDetails - ( + void UpdateCentreManagerDetails( int centreId, string firstName, string lastName, @@ -23,8 +23,7 @@ void UpdateCentreManagerDetails string? telephone ); - void UpdateCentreWebsiteDetails - ( + void UpdateCentreWebsiteDetails( int centreId, string postcode, bool showOnMap, @@ -41,6 +40,8 @@ void UpdateCentreWebsiteDetails (string firstName, string lastName, string email) GetCentreManagerDetails(int centreId); string[] GetCentreIpPrefixes(int centreId); + + IEnumerable GetCentreRanks(DateTime dateSince, int? regionId, int resultsCount, int centreId); } public class CentresDataService : ICentresDataService @@ -56,8 +57,7 @@ public CentresDataService(IDbConnection connection, ILogger public string? GetBannerText(int centreId) { - return connection.QueryFirstOrDefault - ( + return connection.QueryFirstOrDefault( @"SELECT BannerText FROM Centres WHERE CentreID = @centreId", @@ -67,8 +67,7 @@ FROM Centres public string? GetCentreName(int centreId) { - var name = connection.QueryFirstOrDefault - ( + var name = connection.QueryFirstOrDefault( @"SELECT CentreName FROM Centres WHERE CentreID = @centreId", @@ -99,8 +98,7 @@ ORDER BY CentreName" public Centre? GetCentreDetailsById(int centreId) { - var centre = connection.QueryFirstOrDefault - ( + var centre = connection.QueryFirstOrDefault( @"SELECT c.CentreID, c.CentreName, c.RegionID, @@ -132,7 +130,7 @@ ORDER BY CentreName" ct.ContractType FROM Centres AS c INNER JOIN Regions AS r ON r.RegionID = c.RegionID - INNER JOIN ContractTypes as ct on ct.ContractTypeID = c.ContractTypeId + INNER JOIN ContractTypes AS ct ON ct.ContractTypeID = c.ContractTypeId WHERE CentreID = @centreId", new { centreId } ); @@ -151,8 +149,7 @@ FROM Centres AS c return centre; } - public void UpdateCentreManagerDetails - ( + public void UpdateCentreManagerDetails( int centreId, string firstName, string lastName, @@ -160,8 +157,7 @@ public void UpdateCentreManagerDetails string? telephone ) { - connection.Execute - ( + connection.Execute( @"UPDATE Centres SET ContactForename = @firstName, ContactSurname = @lastName, @@ -172,8 +168,7 @@ public void UpdateCentreManagerDetails ); } - public void UpdateCentreWebsiteDetails - ( + public void UpdateCentreWebsiteDetails( int centreId, string postcode, bool showOnMap, @@ -188,8 +183,7 @@ public void UpdateCentreWebsiteDetails string? otherInformation = null ) { - connection.Execute - ( + connection.Execute( @"UPDATE Centres SET pwTelephone = @telephone, pwEmail = @email, @@ -223,8 +217,7 @@ public void UpdateCentreWebsiteDetails public (string firstName, string lastName, string email) GetCentreManagerDetails(int centreId) { - var info = connection.QueryFirstOrDefault<(string, string, string)> - ( + var info = connection.QueryFirstOrDefault<(string, string, string)>( @"SELECT ContactForename, ContactSurname, ContactEmail FROM Centres WHERE CentreID = @centreId", @@ -235,8 +228,7 @@ FROM Centres public string[] GetCentreIpPrefixes(int centreId) { - var ipPrefixString = connection.QueryFirstOrDefault - ( + var ipPrefixString = connection.QueryFirstOrDefault( @"SELECT IPPrefix FROM Centres WHERE CentreID = @centreId", @@ -246,5 +238,45 @@ FROM Centres var ipPrefixes = ipPrefixString?.Split(',', StringSplitOptions.RemoveEmptyEntries); return ipPrefixes ?? new string[0]; } + + public IEnumerable GetCentreRanks( + DateTime dateSince, + int? regionId, + int resultsCount, + int centreId + ) + { + return connection.Query( + @"WITH SessionsCount AS + ( + SELECT + Count(c.CentreID) AS DelegateSessionCount, + c.CentreID + FROM [Sessions] s + INNER JOIN Candidates c ON s.CandidateID = c.CandidateID + INNER JOIN Centres ct ON c.CentreID = ct.CentreID + WHERE + s.LoginTime > @dateSince + AND c.CentreID <> 101 AND c.CentreID <> 374 + AND (ct.RegionID = @RegionID OR @RegionID IS NULL) + GROUP BY c.CentreID + ), + Rankings AS + ( + SELECT + RANK() OVER (ORDER BY sc.DelegateSessionCount DESC) AS Ranking, + c.CentreID, + c.CentreName, + sc.DelegateSessionCount + FROM SessionsCount sc + INNER JOIN Centres c ON sc.CentreID = c.CentreID + ) + SELECT * + FROM Rankings + WHERE Ranking <= @resultsCount OR CentreID = @centreId + ORDER BY Ranking", + new { dateSince, regionId, resultsCount, centreId } + ); + } } } diff --git a/DigitalLearningSolutions.Data/Models/DbModels/CentreRanking.cs b/DigitalLearningSolutions.Data/Models/DbModels/CentreRanking.cs new file mode 100644 index 0000000000..22825569db --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/DbModels/CentreRanking.cs @@ -0,0 +1,13 @@ +namespace DigitalLearningSolutions.Data.Models.DbModels +{ + public class CentreRanking + { + public int CentreId { get; set; } + + public int Ranking { get; set; } + + public string CentreName { get; set; } + + public int DelegateSessionCount { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Services/CentresService.cs b/DigitalLearningSolutions.Data/Services/CentresService.cs new file mode 100644 index 0000000000..b6ec4d4560 --- /dev/null +++ b/DigitalLearningSolutions.Data/Services/CentresService.cs @@ -0,0 +1,43 @@ +namespace DigitalLearningSolutions.Data.Services +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Models.DbModels; + + public interface ICentresService + { + IEnumerable GetCentresForCentreRankingPage(int centreId, int numberOfDays, int? regionId); + + int? GetCentreRankForCentre(int centreId); + } + + public class CentresService : ICentresService + { + private const int NumberOfCentresToDisplay = 10; + private const int DefaultNumberOfDaysFilter = 14; + private readonly ICentresDataService centresDataService; + private readonly IClockService clockService; + + public CentresService(ICentresDataService centresDataService, IClockService clockService) + { + this.centresDataService = centresDataService; + this.clockService = clockService; + } + + public IEnumerable GetCentresForCentreRankingPage(int centreId, int numberOfDays, int? regionId) + { + var dateSince = clockService.UtcNow.AddDays(-numberOfDays); + + return centresDataService.GetCentreRanks(dateSince, regionId, NumberOfCentresToDisplay, centreId).ToList(); + } + + public int? GetCentreRankForCentre(int centreId) + { + var dateSince = clockService.UtcNow.AddDays(-DefaultNumberOfDaysFilter); + var centreRankings = centresDataService.GetCentreRanks(dateSince, null, NumberOfCentresToDisplay, centreId); + var centreRanking = centreRankings.SingleOrDefault(cr => cr.CentreId == centreId); + return centreRanking?.Ranking; + } + } +} diff --git a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs index bc9e6bc2d4..4a5628f540 100644 --- a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs +++ b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs @@ -28,6 +28,7 @@ public void Page_has_no_accessibility_errors(string url, string pageTitle) [InlineData("/MyAccount/EditDetails", "Edit details")] [InlineData("/TrackingSystem/Centre/Administrators", "Centre administrators")] [InlineData("/TrackingSystem/Centre/Dashboard", "Centre dashboard")] + [InlineData("/TrackingSystem/Centre/Ranking", "Centre ranking")] [InlineData("/TrackingSystem/CentreConfiguration", "Centre configuration")] [InlineData("/TrackingSystem/CentreConfiguration/EditCentreManagerDetails", "Edit centre manager details")] [InlineData("/TrackingSystem/CentreConfiguration/EditCentreWebsiteDetails", "Edit centre content on DLS website")] diff --git a/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/CentreRankingViewModelTests.cs b/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/CentreRankingViewModelTests.cs new file mode 100644 index 0000000000..95437af2c4 --- /dev/null +++ b/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/CentreRankingViewModelTests.cs @@ -0,0 +1,91 @@ +namespace DigitalLearningSolutions.Web.Tests.ViewModels.TrackingSystem.Centre +{ + using System.Linq; + using DigitalLearningSolutions.Data.Models.DbModels; + using DigitalLearningSolutions.Data.Tests.TestHelpers; + using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking; + using FluentAssertions; + using FluentAssertions.Execution; + using NUnit.Framework; + + public class CentreRankingViewModelTests + { + private readonly CentreRanking[] centreRankings = + { + CentreTestHelper.GetCentreRank(1), + CentreTestHelper.GetCentreRank(2), + CentreTestHelper.GetCentreRank(3), + CentreTestHelper.GetCentreRank(4), + CentreTestHelper.GetCentreRank(5), + CentreTestHelper.GetCentreRank(6), + CentreTestHelper.GetCentreRank(7), + CentreTestHelper.GetCentreRank(8), + CentreTestHelper.GetCentreRank(9), + CentreTestHelper.GetCentreRank(10) + }; + + [Test] + public void CentreRankingViewModel_populates_expected_values_from_centre_ranks_with_centre_in_top_ten() + { + // When + var result = new CentreRankingViewModel(centreRankings, 3); + + // Then + using (new AssertionScope()) + { + result.Centres.Count().Should().Be(10); + result.CentreHasNoActivity.Should().BeFalse(); + } + } + + [Test] + public void + CentreRankingViewModel_populates_expected_values_from_centre_ranks_with_centre_not_in_top_ten() + { + // Given + var centreRankingsWithExtraCentre = centreRankings.Append(CentreTestHelper.GetCentreRank(20)); + + // When + var result = new CentreRankingViewModel(centreRankingsWithExtraCentre, 20); + + // Then + using (new AssertionScope()) + { + result.Centres.Count().Should().Be(11); + result.CentreHasNoActivity.Should().BeFalse(); + } + } + + [Test] + public void + CentreRankingViewModel_populates_expected_values_from_centre_ranks_when_centre_has_no_data() + { + // When + var result = new CentreRankingViewModel(centreRankings, 20); + + // Then + using (new AssertionScope()) + { + result.Centres.Count().Should().Be(10); + result.CentreHasNoActivity.Should().BeTrue(); + } + } + + [Test] + public void CentreRankingViewModel_populates_expected_values_from_centre_ranks_with_less_data() + { + // Given + var shortedCentreRankings = centreRankings.Take(5); + + // When + var result = new CentreRankingViewModel(shortedCentreRankings, 20); + + // Then + using (new AssertionScope()) + { + result.Centres.Count().Should().Be(5); + result.CentreHasNoActivity.Should().BeTrue(); + } + } + } +} diff --git a/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/DashboardCentreDetailsViewModelTests.cs b/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/DashboardCentreDetailsViewModelTests.cs index 651bceefde..7aaf2f249c 100644 --- a/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/DashboardCentreDetailsViewModelTests.cs +++ b/DigitalLearningSolutions.Web.Tests/ViewModels/TrackingSystem/Centre/DashboardCentreDetailsViewModelTests.cs @@ -14,7 +14,7 @@ public void DashboardCentreDetailsViewModel_populates_expected_values_from_centr const string userIp = "1.1.1.1"; var centre = CentreTestHelper.GetDefaultCentre(); - var viewModel = new DashboardCentreDetailsViewModel(centre, userIp); + var viewModel = new DashboardCentreDetailsViewModel(centre, userIp, 12); // Then using (new AssertionScope()) @@ -29,6 +29,32 @@ public void DashboardCentreDetailsViewModel_populates_expected_values_from_centr viewModel.ApprovedIps.Should().BeEquivalentTo(centre.IpPrefix); viewModel.Telephone.Should().BeEquivalentTo(centre.ContactTelephone); viewModel.Email.Should().BeEquivalentTo(centre.ContactEmail); + viewModel.CentreRank.Should().Be("12"); + } + } + + [Test] + public void DashboardCentreDetailsViewModel_populates_expected_values_from_centre_with_null_centre_rank() + { + const string userIp = "1.1.1.1"; + var centre = + CentreTestHelper.GetDefaultCentre(); + var viewModel = new DashboardCentreDetailsViewModel(centre, userIp, null); + + // Then + using (new AssertionScope()) + { + viewModel.CentreId.Should().Be(centre.CentreId); + viewModel.CentreName.Should().BeEquivalentTo(centre.CentreName); + viewModel.Region.Should().BeEquivalentTo(centre.RegionName); + viewModel.ContractType.Should().BeEquivalentTo(centre.ContractType); + viewModel.BannerText.Should().BeEquivalentTo(centre.BannerText); + viewModel.CentreManager.Should().BeEquivalentTo("xxxxx xxxx"); + viewModel.IpAddress.Should().BeEquivalentTo(userIp); + viewModel.ApprovedIps.Should().BeEquivalentTo(centre.IpPrefix); + viewModel.Telephone.Should().BeEquivalentTo(centre.ContactTelephone); + viewModel.Email.Should().BeEquivalentTo(centre.ContactEmail); + viewModel.CentreRank.Should().Be("No activity"); } } } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/DashboardController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs similarity index 77% rename from DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/DashboardController.cs rename to DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs index 1b8ab81103..69a02d9250 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/DashboardController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs @@ -1,4 +1,4 @@ -namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre.Dashboard { using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Services; @@ -12,22 +12,24 @@ public class DashboardController : Controller { private readonly ICentresDataService centresDataService; - private readonly IUserDataService userDataService; + private readonly ICentresService centresService; private readonly ICourseDataService courseDataService; private readonly ISupportTicketDataService ticketDataService; + private readonly IUserDataService userDataService; - public DashboardController - ( + public DashboardController( IUserDataService userDataService, ICentresDataService centresDataService, ICourseDataService courseDataService, - ISupportTicketDataService ticketDataService + ISupportTicketDataService ticketDataService, + ICentresService centresService ) { this.userDataService = userDataService; this.centresDataService = centresDataService; this.courseDataService = courseDataService; this.ticketDataService = ticketDataService; + this.centresService = centresService; } public IActionResult Index() @@ -36,10 +38,12 @@ public IActionResult Index() var centreId = User.GetCentreId(); var centre = centresDataService.GetCentreDetailsById(centreId)!; var delegateCount = userDataService.GetNumberOfApprovedDelegatesAtCentre(centreId); - var courseCount = courseDataService.GetNumberOfActiveCoursesAtCentreForCategory(centreId, adminUser.CategoryId); + var courseCount = + courseDataService.GetNumberOfActiveCoursesAtCentreForCategory(centreId, adminUser.CategoryId); var adminCount = userDataService.GetNumberOfActiveAdminsAtCentre(centreId); var supportTicketCount = ticketDataService.GetNumberOfUnarchivedTicketsForCentreId(centreId); - + var centreRank = centresService.GetCentreRankForCentre(centreId); + var model = new CentreDashboardViewModel( centre, adminUser.FirstName, @@ -48,7 +52,8 @@ public IActionResult Index() delegateCount, courseCount, adminCount, - supportTicketCount + supportTicketCount, + centreRank ); return View(model); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/RankingController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/RankingController.cs new file mode 100644 index 0000000000..de094bdae0 --- /dev/null +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/RankingController.cs @@ -0,0 +1,29 @@ +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Centre.Dashboard +{ + using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + [Authorize(Policy = CustomPolicies.UserCentreAdmin)] + [Route("/TrackingSystem/Centre/Ranking")] + public class RankingController : Controller + { + private readonly ICentresService centresService; + + public RankingController(ICentresService centresService) + { + this.centresService = centresService; + } + + public IActionResult Index() + { + var centreId = User.GetCentreId(); + // TODO: HEEDLS-469 Populate these numbers from filters + var centreRankings = centresService.GetCentresForCentreRankingPage(centreId, 14, null); + + return View(new CentreRankingViewModel(centreRankings, centreId)); + } + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs b/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs index 738867bf64..f368d8b78e 100644 --- a/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/CustomClaimHelper.cs @@ -74,14 +74,18 @@ public static bool IsDelegateOnlyAccount(this ClaimsPrincipal user) public static bool HasCentreAdminPermissions(this ClaimsPrincipal user) { return (user.GetCustomClaimAsBool(CustomClaimTypes.UserCentreAdmin) ?? false) || - (user.GetCustomClaimAsBool(CustomClaimTypes.UserCentreManager) ?? false) || - (user.GetCustomClaimAsBool(CustomClaimTypes.UserUserAdmin) ?? false); + user.HasCentreManagerPermissions(); } public static bool HasCentreManagerPermissions(this ClaimsPrincipal user) { return (user.GetCustomClaimAsBool(CustomClaimTypes.UserCentreManager) ?? false) || - (user.GetCustomClaimAsBool(CustomClaimTypes.UserUserAdmin) ?? false); + user.HasSuperAdminPermissions(); + } + + public static bool HasSuperAdminPermissions(this ClaimsPrincipal user) + { + return user.GetCustomClaimAsBool(CustomClaimTypes.UserUserAdmin) ?? false; } } } diff --git a/DigitalLearningSolutions.Web/Helpers/CustomPolicies.cs b/DigitalLearningSolutions.Web/Helpers/CustomPolicies.cs index e24e8b57bc..2e9c094672 100644 --- a/DigitalLearningSolutions.Web/Helpers/CustomPolicies.cs +++ b/DigitalLearningSolutions.Web/Helpers/CustomPolicies.cs @@ -44,8 +44,7 @@ AuthorizationPolicyBuilder policy { return policy.RequireAssertion( context => context.User.GetCustomClaimAsInt(CustomClaimTypes.UserAdminId) != null && - (context.User.GetCustomClaimAsBool(CustomClaimTypes.UserCentreManager) == true || - context.User.GetCustomClaimAsBool(CustomClaimTypes.UserUserAdmin) == true) + context.User.HasCentreManagerPermissions() ); } } diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index 0803d8b771..e8c5127b65 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -126,6 +126,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(_ => new SqlConnection(defaultConnectionString)); // Register services. + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/centreRanking.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/centreRanking.scss new file mode 100644 index 0000000000..6cf96b8297 --- /dev/null +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/centreRanking.scss @@ -0,0 +1,17 @@ +@import "~nhsuk-frontend/packages/core/all"; + +$current-centre-highlight: $color_nhsuk-pale-yellow; + +.current-centre { + background-color: $current-centre-highlight; + + &:hover { + background-color: $current-centre-highlight; + } +} + +.cell-right-padding { + @include govuk-media-query($until: desktop) { + padding-right: nhsuk-spacing(2) !important; + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/CentreDashboardViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/CentreDashboardViewModel.cs index af4c67c3d7..6409a0d516 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/CentreDashboardViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/CentreDashboardViewModel.cs @@ -13,10 +13,11 @@ public CentreDashboardViewModel int delegates, int courses, int admins, - int supportTickets + int supportTickets, + int? centreRank ) { - CentreDetails = new DashboardCentreDetailsViewModel(centre, userIpAddress); + CentreDetails = new DashboardCentreDetailsViewModel(centre, userIpAddress, centreRank); FirstName = string.IsNullOrWhiteSpace(firstName) ? "User" : firstName; CourseCategory = categoryName ?? "all"; NumberOfDelegates = delegates; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/DashboardCentreDetailsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/DashboardCentreDetailsViewModel.cs index dad8f2da69..8962d8e055 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/DashboardCentreDetailsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Dashboard/DashboardCentreDetailsViewModel.cs @@ -4,7 +4,7 @@ public class DashboardCentreDetailsViewModel { - public DashboardCentreDetailsViewModel(Centre centre, string userIpAddress) + public DashboardCentreDetailsViewModel(Centre centre, string userIpAddress, int? centreRank) { CentreName = centre.CentreName; CentreId = centre.CentreId; @@ -16,6 +16,7 @@ public DashboardCentreDetailsViewModel(Centre centre, string userIpAddress) BannerText = centre.BannerText; IpAddress = userIpAddress; ApprovedIps = centre.IpPrefix; + CentreRank = centreRank?.ToString() ?? "No activity"; } public string CentreName { get; set; } @@ -37,5 +38,7 @@ public DashboardCentreDetailsViewModel(Centre centre, string userIpAddress) public string IpAddress { get; set; } public string? ApprovedIps { get; set; } + + public string CentreRank { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankViewModel.cs new file mode 100644 index 0000000000..06dea9a82d --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankViewModel.cs @@ -0,0 +1,21 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking +{ + public class CentreRankViewModel + { + public CentreRankViewModel(int rank, string centreName, int sum, bool isHighlighted) + { + Rank = rank; + CentreName = centreName; + Sum = sum; + IsHighlighted = isHighlighted; + } + + public int Rank { get; set; } + + public string CentreName { get; set; } + + public int Sum { get; set; } + + public bool IsHighlighted { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankingViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankingViewModel.cs new file mode 100644 index 0000000000..8ee9d21b0d --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Ranking/CentreRankingViewModel.cs @@ -0,0 +1,30 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.Models.DbModels; + + public class CentreRankingViewModel + { + public CentreRankingViewModel(IEnumerable centreRanks, int centreId) + { + var centreRanksList = centreRanks.ToList(); + + Centres = centreRanksList.OrderBy(cr => cr.Ranking) + .Select( + cr => new CentreRankViewModel( + cr.Ranking, + cr.CentreName, + cr.DelegateSessionCount, + cr.CentreId == centreId + ) + ); + + CentreHasNoActivity = centreRanksList.All(cr => cr.CentreId != centreId); + } + + public IEnumerable Centres { get; set; } + + public bool CentreHasNoActivity { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml index 739a02bd5b..5471d18df9 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardBottomCardGroup.cshtml @@ -40,7 +40,7 @@

- Centre ranking + Centre ranking

See centre rankings by week, month, or year

diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardCentreDetails.cshtml index ca1e50ae03..f193f650bc 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Dashboard/_DashboardCentreDetails.cshtml @@ -12,6 +12,15 @@
+
+
+ Centre ranking +
+
+ @Model.CentreDetails.CentreRank +
+
+
Centre name diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/Index.cshtml new file mode 100644 index 0000000000..36589b79da --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/Index.cshtml @@ -0,0 +1,41 @@ +@inject IConfiguration Configuration + +@using DigitalLearningSolutions.Web.Helpers +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking +@using Microsoft.Extensions.Configuration +@model CentreRankingViewModel + + + +@{ + ViewData["Title"] = "Centre ranking"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +@section NavBreadcrumbs { + +} + +
+
+

Centre ranking

+ +

The top ten Digital Learning Solutions centres by learner course launches.

+ + @if (User.HasSuperAdminPermissions()) { + + } else { + + } + + @if (Model.CentreHasNoActivity) { +

Your centre overall rank: no activity

+ } +
+
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingStandardUserTable.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingStandardUserTable.cshtml new file mode 100644 index 0000000000..c9b62bf71c --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingStandardUserTable.cshtml @@ -0,0 +1,23 @@ +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking +@model CentreRankingViewModel + + + + + + + + + + @foreach (var centre in Model.Centres) { + + + + + } + +
+ Rank + + Centre +
@centre.Rank@centre.CentreName
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingSuperAdminTable.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingSuperAdminTable.cshtml new file mode 100644 index 0000000000..df4faad5f6 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Ranking/_RankingSuperAdminTable.cshtml @@ -0,0 +1,33 @@ +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Ranking +@model CentreRankingViewModel + + + + + + + + + + + @foreach (var centre in Model.Centres) { + + + + + + } + +
+ Rank + + Centre + + Sum +
+ Rank @centre.Rank + + Centre @centre.CentreName + + Sum @centre.Sum +
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_CentreDashboardBreadcrumbs.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_CentreDashboardBreadcrumbs.cshtml new file mode 100644 index 0000000000..71ac9e063d --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_CentreDashboardBreadcrumbs.cshtml @@ -0,0 +1,12 @@ + diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_NavMenuItems.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_NavMenuItems.cshtml index b6f4efbc9e..71d59000ef 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_NavMenuItems.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Shared/_NavMenuItems.cshtml @@ -22,7 +22,7 @@ } -@if (User.GetCustomClaimAsBool(CustomClaimTypes.UserUserAdmin) ?? false) { +@if (User.HasSuperAdminPermissions()) {
  • Admin