From 9f585c383dc7735d8360a5a0dce5bab6d2501367 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 11 Mar 2025 21:55:02 +0000 Subject: [PATCH 01/64] TD-3711 Adding Last Accessed Columns To Tables --- .../202503111500_AddLastAccessedColumn.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs diff --git a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs new file mode 100644 index 0000000000..f17a33bb19 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs @@ -0,0 +1,26 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202503111500)] + public class AddLastAccessedColumn : Migration + { + public override void Up() + { + Alter.Table("Users").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("DelegateAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("AdminAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + + Execute.Sql("UPDATE u SET LastAccessed = (SELECT MAX(s.LoginTime) FROM DelegateAccounts da JOIN Sessions s ON da.ID = s.CandidateId WHERE da.UserID = u.ID) FROM users u;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT MAX(s.LoginTime) FROM Sessions s WHERE s.CandidateId = da.ID) FROM DelegateAccounts da;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT ca.LastAccessed FROM CandidateAssessments ca WHERE ca.ID = da.ID) FROM DelegateAccounts da where da.LastAccessed IS NULL;"); + Execute.Sql("UPDATE AA SET LastAccessed = (SELECT MAX(AdS.LoginTime) FROM AdminSessions AdS WHERE AdS.AdminID = AA.ID) FROM AdminAccounts AA;"); + } + public override void Down() + { + Delete.Column("LastAccessed").FromTable("Competencies"); + Delete.Column("LastAccessed").FromTable("DelegateAccounts"); + Delete.Column("LastAccessed").FromTable("AdminAccounts"); + } + } +} From 312ef004c9b6329b9b95e563a5062b06e341e585 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 10:21:01 +0000 Subject: [PATCH 02/64] TD-3712 Update LastAccessed Date For Users During Login --- .../DataServices/LoginDataService.cs | 33 +++++++++++++++++++ .../Services/LoginServiceTests.cs | 4 ++- .../Controllers/LoginController.cs | 7 ++++ .../Services/LoginService.cs | 13 +++++++- DigitalLearningSolutions.Web/Startup.cs | 1 + 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 DigitalLearningSolutions.Data/DataServices/LoginDataService.cs diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs new file mode 100644 index 0000000000..93cd3abf6e --- /dev/null +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -0,0 +1,33 @@ +namespace DigitalLearningSolutions.Data.DataServices +{ + using Dapper; + using System.Data; + + public interface ILoginDataService + { + void UpdateLastAccessedForUsersTable(int Id); + } + + public class LoginDataService : ILoginDataService + { + private readonly IDbConnection connection; + + public LoginDataService(IDbConnection connection) + { + this.connection = connection; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + connection.Execute( + @"UPDATE Users SET + LastAccessed = GETDATE() + WHERE ID = @Id", + new + { + Id + } + ); + } + } +} diff --git a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs index f8dad9821e..a77c38f7b8 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; @@ -32,6 +33,7 @@ private static readonly (string?, List<(int centreId, string centreName, string private LoginService loginService = null!; private IUserService userService = null!; private IUserVerificationService userVerificationService = null!; + private ILoginDataService loginDataService = null!; [SetUp] public void Setup() @@ -39,7 +41,7 @@ public void Setup() userVerificationService = A.Fake(x => x.Strict()); userService = A.Fake(x => x.Strict()); - loginService = new LoginService(userService, userVerificationService); + loginService = new LoginService(userService, userVerificationService, loginDataService); } [Test] diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 20280e18a8..284530a481 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -82,6 +82,13 @@ public async Task Index(LoginViewModel model, string timeZone = " var loginResult = loginService.AttemptLogin(model.Username!.Trim(), model.Password!); + if (loginResult.LoginAttemptResult == LoginAttemptResult.LogIntoSingleCentre || + loginResult.LoginAttemptResult == LoginAttemptResult.ChooseACentre) + { + loginService.UpdateLastAccessedForUsersTable(loginResult.UserEntity.UserAccount.Id); + } + + switch (loginResult.LoginAttemptResult) { case LoginAttemptResult.InvalidCredentials: diff --git a/DigitalLearningSolutions.Web/Services/LoginService.cs b/DigitalLearningSolutions.Web/Services/LoginService.cs index 35d8cc7a16..6960e920cf 100644 --- a/DigitalLearningSolutions.Web/Services/LoginService.cs +++ b/DigitalLearningSolutions.Web/Services/LoginService.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Data.ViewModels; using DigitalLearningSolutions.Web.Helpers; using Microsoft.AspNetCore.Authentication; @@ -28,6 +30,8 @@ List idsOfCentresWithUnverifiedEmails bool CentreEmailIsVerified(int userId, int centreIdIfLoggingIntoSingleCentre); + void UpdateLastAccessedForUsersTable(int Id); + Task HandleLoginResult( LoginResult loginResult, TicketReceivedContext context, @@ -41,11 +45,18 @@ public class LoginService : ILoginService { private readonly IUserService userService; private readonly IUserVerificationService userVerificationService; + private readonly ILoginDataService loginDataService; - public LoginService(IUserService userService, IUserVerificationService userVerificationService) + public LoginService(IUserService userService, IUserVerificationService userVerificationService, ILoginDataService loginDataService) { this.userService = userService; this.userVerificationService = userVerificationService; + this.loginDataService = loginDataService; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + loginDataService.UpdateLastAccessedForUsersTable(Id); } public LoginResult AttemptLogin(string username, string password) diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index bdc34b4d79..07b0c84237 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -534,6 +534,7 @@ private static void RegisterDataServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void RegisterHelpers(IServiceCollection services) From 527973e5615ea36282903e00f6ae6ff8eefbcf75 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 14:59:04 +0000 Subject: [PATCH 03/64] TD-3711 Correcting table name --- .../202503111500_AddLastAccessedColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs index f17a33bb19..95a0eb6020 100644 --- a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs +++ b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs @@ -18,7 +18,7 @@ public override void Up() } public override void Down() { - Delete.Column("LastAccessed").FromTable("Competencies"); + Delete.Column("LastAccessed").FromTable("Users"); Delete.Column("LastAccessed").FromTable("DelegateAccounts"); Delete.Column("LastAccessed").FromTable("AdminAccounts"); } From 52cb242344678d83bd821699769e29bff2e6bed0 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Wed, 12 Mar 2025 15:45:31 +0000 Subject: [PATCH 04/64] TD-3712 Correcting GetUTCDate() --- DigitalLearningSolutions.Data/DataServices/LoginDataService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs index 93cd3abf6e..37faf67778 100644 --- a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -21,7 +21,7 @@ public void UpdateLastAccessedForUsersTable(int Id) { connection.Execute( @"UPDATE Users SET - LastAccessed = GETDATE() + LastAccessed = GetUtcDate() WHERE ID = @Id", new { From 66d8aed518392d12a7d58693548e4c5e6fc5a388 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 15:57:27 +0000 Subject: [PATCH 05/64] TD-3713- Update LastAccessed Date For AdminAccounts And DelegateAccounts --- .../DataServices/LoginDataService.cs | 30 +++++++++++++++++++ .../Controllers/LoginController.cs | 6 ++-- .../Services/LoginService.cs | 14 +++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs index 37faf67778..109d5a9273 100644 --- a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -6,6 +6,10 @@ public interface ILoginDataService { void UpdateLastAccessedForUsersTable(int Id); + + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); } public class LoginDataService : ILoginDataService @@ -29,5 +33,31 @@ public void UpdateLastAccessedForUsersTable(int Id) } ); } + + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + connection.Execute( + @"UPDATE DelegateAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + connection.Execute( + @"UPDATE AdminAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 284530a481..f9fb365dff 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -226,11 +226,13 @@ int centreIdToLogInto IsPersistent = rememberMe, IssuedUtc = clockUtility.UtcNow, }; + var centreAccountSet = userEntity?.GetCentreAccountSet(centreIdToLogInto); + loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); - var adminAccount = userEntity!.GetCentreAccountSet(centreIdToLogInto)?.AdminAccount; - + var adminAccount = centreAccountSet?.AdminAccount; if (adminAccount?.Active == true) { + loginService.UpdateLastAccessedForAdminAccountsTable(adminAccount.Id); sessionService.StartAdminSession(adminAccount.Id); } diff --git a/DigitalLearningSolutions.Web/Services/LoginService.cs b/DigitalLearningSolutions.Web/Services/LoginService.cs index 6960e920cf..dc52aca0b5 100644 --- a/DigitalLearningSolutions.Web/Services/LoginService.cs +++ b/DigitalLearningSolutions.Web/Services/LoginService.cs @@ -32,6 +32,10 @@ List idsOfCentresWithUnverifiedEmails void UpdateLastAccessedForUsersTable(int Id); + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); + Task HandleLoginResult( LoginResult loginResult, TicketReceivedContext context, @@ -59,6 +63,16 @@ public void UpdateLastAccessedForUsersTable(int Id) loginDataService.UpdateLastAccessedForUsersTable(Id); } + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForDelegatesAccountsTable(Id); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForAdminAccountsTable(Id); + } + public LoginResult AttemptLogin(string username, string password) { var userEntity = userService.GetUserByUsername(username); From 01774db0ec21281d0c406d6849d054f140324ee1 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 16:55:29 +0000 Subject: [PATCH 06/64] TD-3713 Addressing test fail for the case when only admin account is present --- DigitalLearningSolutions.Web/Controllers/LoginController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index f9fb365dff..968535144d 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -227,7 +227,11 @@ int centreIdToLogInto IssuedUtc = clockUtility.UtcNow, }; var centreAccountSet = userEntity?.GetCentreAccountSet(centreIdToLogInto); - loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); + + if (centreAccountSet?.DelegateAccount?.Id != null) + { + loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); + } var adminAccount = centreAccountSet?.AdminAccount; if (adminAccount?.Active == true) From be28d6d96b84e9a46fcc3c9a4145c187cfa0c956 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 17:15:01 +0000 Subject: [PATCH 07/64] TD-3714 Adding LastAccessed Date To Delegates Card In Tracking System --- .../DelegateUserCardDataService.cs | 1 + .../Models/User/DelegateAccount.cs | 1 + .../Models/User/DelegateUser.cs | 1 + .../Models/User/DelegateUserCard.cs | 1 + .../Delegates/Shared/DelegateInfoViewModel.cs | 5 + .../_SearchableDelegateCard.cshtml | 159 +++++++++--------- 6 files changed, 92 insertions(+), 76 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 8e2a424ed5..40556ef3cb 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -87,6 +87,7 @@ FROM DelegateAccounts AS da c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs index 3f720e350b..55dc4b25cc 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs @@ -12,6 +12,7 @@ public class DelegateAccount public bool CentreActive { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime DateRegistered { get; set; } + public DateTime LastAccessed { get; set; } public string? Answer1 { get; set; } public string? Answer2 { get; set; } public string? Answer3 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs index 7033b8d13f..f007b355b1 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs @@ -9,6 +9,7 @@ public class DelegateUser : User public int UserId { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime? DateRegistered { get; set; } + public DateTime? LastAccessed { get; set; } public int JobGroupId { get; set; } public string? JobGroupName { get; set; } public string? Answer1 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs index ed04402308..1e9caf4e9d 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs @@ -23,6 +23,7 @@ public DelegateUserCard(DelegateEntity delegateEntity) Password = delegateEntity.UserAccount.PasswordHash; CandidateNumber = delegateEntity.DelegateAccount.CandidateNumber; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; JobGroupId = delegateEntity.UserAccount.JobGroupId; JobGroupName = delegateEntity.UserAccount.JobGroupName; Answer1 = delegateEntity.DelegateAccount.Answer1; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs index 504fd4b454..814e488ac9 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs @@ -41,6 +41,10 @@ IEnumerable delegateRegistrationPrompts { RegistrationDate = delegateUser.DateRegistered.Value.ToString(DateHelper.StandardDateFormat); } + if (delegateUser.LastAccessed.HasValue) + { + LastAccessed = delegateUser.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } DelegateRegistrationPrompts = delegateRegistrationPrompts; RegistrationConfirmationHash = delegateUser.RegistrationConfirmationHash; @@ -61,6 +65,7 @@ IEnumerable delegateRegistrationPrompts public int JobGroupId { get; set; } public string? JobGroup { get; set; } public string? RegistrationDate { get; set; } + public string? LastAccessed { get; set; } public string ProfessionalRegistrationNumber { get; set; } public string? RegistrationConfirmationHash { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml index 13f17d0ae7..4064cd80bd 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/AllDelegates/_SearchableDelegateCard.cshtml @@ -4,91 +4,98 @@
-
- - - @Model.DelegateInfo.TitleName - @if (Model.DelegateInfo.Email == null) - @("(Email address not set)") - else - @DisplayStringHelper.GetEmailDisplayString(Model.DelegateInfo.Email) +
+ + + @Model.DelegateInfo.TitleName + @if (Model.DelegateInfo.Email == null) + @("(Email address not set)") + else + @DisplayStringHelper.GetEmailDisplayString(Model.DelegateInfo.Email) - - + +
-
+
- + -
-
-
- Name -
- -
+
+
+
+ Name +
+ +
-
-
- Email -
- -
+
+
+ Email +
+ +
-
-
- ID -
- -
+
+
+ ID +
+ +
-
-
- Registration date -
- - -
+
+
+ Registration date +
+ + +
-
-
- Job group -
- - -
+
+
+ Last Accessed date +
+ +
-
-
- Professional Registration Number -
- -
+
+
+ Job group +
+ + +
- @foreach (var delegateRegistrationPrompt in Model.DelegateInfo.DelegateRegistrationPrompts) - { -
-
@delegateRegistrationPrompt.Prompt
- - @if (Model.RegistrationPromptFilters.ContainsKey(delegateRegistrationPrompt.PromptNumber)) - { - - } -
- } -
+
+
+ Professional Registration Number +
+ +
- Manage delegate - - Set password - -
-
+ @foreach (var delegateRegistrationPrompt in Model.DelegateInfo.DelegateRegistrationPrompts) + { +
+
@delegateRegistrationPrompt.Prompt
+ + @if (Model.RegistrationPromptFilters.ContainsKey(delegateRegistrationPrompt.PromptNumber)) + { + + } +
+ } + + + Manage delegate + + Set password + +
+ From 5d4dd9c10ceb4d6abbb9ec7d91a304126dbcf3cb Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 13 Mar 2025 21:11:08 +0000 Subject: [PATCH 08/64] TD-3716-Adding LastAccessed Date In Admin Card --- .../DataServices/UserDataService/AdminUserDataService.cs | 1 + DigitalLearningSolutions.Data/Models/User/AdminAccount.cs | 3 +++ DigitalLearningSolutions.Data/Models/User/AdminEntity.cs | 2 ++ .../Centre/Administrator/SearchableAdminViewModel.cs | 8 ++++++++ .../Centre/Administrator/_SearchableAdminCard.cshtml | 8 ++++++++ 5 files changed, 22 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index d93094c97b..9378fcc04c 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -105,6 +105,7 @@ FROM AdminAccounts AS aa aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, + aa.LastAccessed, CASE WHEN aa.CategoryID IS NULL THEN 'All' ELSE cc.CategoryName diff --git a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs index eaff1da52f..51a109785b 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs @@ -1,5 +1,7 @@ namespace DigitalLearningSolutions.Data.Models.User { + using System; + public class AdminAccount { public int Id { get; set; } @@ -26,6 +28,7 @@ public class AdminAccount public bool IsWorkforceContributor { get; set; } public bool IsLocalWorkforceManager { get; set; } public bool IsNominatedSupervisor { get; set; } + public DateTime? LastAccessed { get; set; } public bool IsCmsAdministrator => ImportOnly && IsContentManager; public bool IsCmsManager => IsContentManager && !ImportOnly; diff --git a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs index e070aca124..a9b71e5143 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs @@ -3,6 +3,7 @@ using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; + using System; public class AdminEntity : BaseSearchableItem { @@ -72,6 +73,7 @@ public override string SearchableName public bool IsSuperAdmin => AdminAccount.IsSuperAdmin; public bool IsReportsViewer => AdminAccount.IsReportsViewer; public bool IsActive => AdminAccount.Active; + public DateTime? LastAccessed => AdminAccount.LastAccessed; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs index 7e4ab30b47..c3da68c395 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs @@ -5,6 +5,8 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + using System; + using DateHelper = Helpers.DateHelper; public class SearchableAdminViewModel : BaseFilterableViewModel { @@ -23,6 +25,10 @@ ReturnPageQuery returnPageQuery EmailAddress = admin.EmailForCentreNotifications; IsLocked = admin.UserAccount.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsActive = admin.AdminAccount.Active; + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = UserPermissionsHelper.LoggedInAdminCanDeactivateUser(admin.AdminAccount, loggedInAdminAccount); @@ -46,6 +52,8 @@ ReturnPageQuery returnPageQuery public bool IsActive { get; set; } + public string? LastAccessed { get; set; } + public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml index 420378260a..fe44f17379 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/Administrator/_SearchableAdminCard.cshtml @@ -35,6 +35,14 @@ @Model.CategoryName +
+
+ Last accessed +
+
+ @(Model.LastAccessed != null ? Model.LastAccessed : "-") +
+
@if (Model.IsLocked) From 6b805b7ba3a4f1f31d73b4742a877d5b39a1791c Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Fri, 14 Mar 2025 08:57:06 +0000 Subject: [PATCH 09/64] TD-3717 Adding LastAccessed Date In SuperAdmin UserAccounts Card --- .../DataServices/UserDataService/UserDataService.cs | 1 + DigitalLearningSolutions.Data/Models/User/UserAccount.cs | 1 + .../SuperAdmin/Users/SearchableUserAccountViewModel.cs | 5 +++++ .../Views/SuperAdmin/Users/_SearchableUserCard.cshtml | 6 ++++++ 4 files changed, 13 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index aba887ccf5..fdc974094b 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -308,6 +308,7 @@ public partial class UserDataService : IUserDataService u.ProfessionalRegistrationNumber, u.ProfileImage, u.Active, + u.LastAccessed, u.ResetPasswordID, u.TermsAgreed, u.FailedLoginCount, diff --git a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs index 4e1c975bf1..6afca34e14 100644 --- a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs @@ -14,6 +14,7 @@ public class UserAccount public string? ProfessionalRegistrationNumber { get; set; } public byte[]? ProfileImage { get; set; } public bool Active { get; set; } + public DateTime? LastAccessed { get; set; } public int? ResetPasswordId { get; set; } public DateTime? TermsAgreed { get; set; } public int FailedLoginCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs index 5b1e7e63ac..263e008165 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs @@ -27,6 +27,10 @@ ReturnPageQuery returnPageQuery ProfessionalRegistrationNumber = user.UserAccount.ProfessionalRegistrationNumber; LearningHubAuthId = user.UserAccount.LearningHubAuthId; ReturnPageQuery = returnPageQuery; + if (user.UserAccount.LastAccessed.HasValue) + { + LastAccessed = user.UserAccount.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } } public int Id { get; set; } @@ -51,6 +55,7 @@ ReturnPageQuery returnPageQuery public int? LearningHubAuthId { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml index 959e8c2060..521b421106 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/Users/_SearchableUserCard.cshtml @@ -86,6 +86,12 @@ +
+
+ Last Accessed +
+ +
Job Group From 1f17db28f9d3ea8b2a4f66c82d4bbe7dcd72bd39 Mon Sep 17 00:00:00 2001 From: rshrirohit <126668828+rshrirohit@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:01:39 +0000 Subject: [PATCH 10/64] TD-3714 Making LastAccessed date nullable in model --- DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs index 55dc4b25cc..66d458fd13 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs @@ -12,7 +12,7 @@ public class DelegateAccount public bool CentreActive { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime DateRegistered { get; set; } - public DateTime LastAccessed { get; set; } + public DateTime? LastAccessed { get; set; } public string? Answer1 { get; set; } public string? Answer2 { get; set; } public string? Answer3 { get; set; } From 94bb54bf7aa10a9a12951b82ae75081b48d94df7 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Fri, 14 Mar 2025 16:03:14 +0000 Subject: [PATCH 11/64] TD-3715 Including LastAccessed Date In Delegates Export --- .../UserDataService/DelegateUserCardDataService.cs | 1 + .../Services/DelegateDownloadFileService.cs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 40556ef3cb..6d08cfc6ae 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -125,6 +125,7 @@ FROM AdminAccounts aa c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, diff --git a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs index 7274173d88..e53ee74932 100644 --- a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs +++ b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs @@ -39,6 +39,7 @@ public class DelegateDownloadFileService : IDelegateDownloadFileService private const string ProfessionalRegistrationNumber = "Professional Registration Number"; private const string JobGroup = "Job group"; private const string RegisteredDate = "Registered"; + private const string LastAccessed = "Last Accessed"; private const string RegistrationComplete = "Registration complete"; private const string Active = "Active"; private const string Approved = "Approved"; @@ -333,6 +334,7 @@ DataTable dataTable new DataColumn(ProfessionalRegistrationNumber), new DataColumn(JobGroup), new DataColumn(RegisteredDate), + new DataColumn(LastAccessed), } ); @@ -374,7 +376,7 @@ CentreRegistrationPrompts registrationPrompts ); row[JobGroup] = delegateRecord.JobGroupName; row[RegisteredDate] = delegateRecord.DateRegistered?.Date; - + row[LastAccessed] = delegateRecord.LastAccessed?.Date; var delegateAnswers = delegateRecord.GetRegistrationFieldAnswers(); foreach (var prompt in registrationPrompts.CustomPrompts) @@ -402,7 +404,7 @@ CentreRegistrationPrompts registrationPrompts private static void FormatAllDelegateWorksheetColumns(IXLWorkbook workbook, DataTable dataTable) { ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, RegisteredDate, XLDataType.DateTime); - + ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, LastAccessed, XLDataType.DateTime); var boolColumns = new[] { RegistrationComplete, Active, Approved, IsAdmin }; foreach (var columnName in boolColumns) { From 1f0d1da79b5c81972cb792418fd3e116324c383d Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Sun, 16 Mar 2025 14:32:47 +0000 Subject: [PATCH 12/64] TD-3718 Adding LastAccessed Date in SuperAdmin Administrators Card --- .../DataServices/UserDataService/AdminUserDataService.cs | 2 +- .../Administrators/SearchableAdminAccountsViewModel.cs | 8 ++++++-- .../AdminAccounts/_SearchableAdminAccountsCard.cshtml | 8 ++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index 9378fcc04c..9f293ad394 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -370,7 +370,7 @@ public IEnumerable GetAdminAccountsByUserId(int userId) } string BaseSelectQuery = $@"SELECT aa.ID, aa.UserID, aa.CentreID, aa.Active, aa.IsCentreAdmin, aa.IsReportsViewer, aa.IsSuperAdmin, aa.IsCentreManager, - aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, + aa.LastAccessed, aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, aa.IsWorkforceManager, aa.IsWorkforceContributor, aa.IsLocalWorkforceManager, aa.IsNominatedSupervisor, u.ID, u.PrimaryEmail, u.FirstName, u.LastName, u.Active, u.FailedLoginCount, c.CentreID, c.CentreName, diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs index 0afcfe7498..88f5ea2889 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs @@ -5,7 +5,7 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; - + using DateHelper = Helpers.DateHelper; public class SearchableAdminAccountsViewModel : BaseFilterableViewModel { public readonly bool CanShowDeleteAdminButton; @@ -28,7 +28,10 @@ ReturnPageQuery returnPageQuery IsLocked = admin.UserAccount?.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsAdminActive = admin.AdminAccount.Active; IsUserActive = admin.UserAccount.Active; - + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = IsAdminActive && admin.AdminIdReferenceCount > 0; CanShowDeleteAdminButton = admin.AdminIdReferenceCount == 0; @@ -47,6 +50,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public bool IsAdminActive { get; set; } public bool IsUserActive { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml index be0fc62d7f..50946491a4 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/AdminAccounts/_SearchableAdminAccountsCard.cshtml @@ -74,6 +74,14 @@
+
+
+ Last Accessed +
+ +
+
+
User Account From 30bebd048333412d6bbffdb350f64d74c8def559 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 18 Mar 2025 09:25:44 +0000 Subject: [PATCH 13/64] TD-3719 Including LastAccessed Date In SuperAdmin Delegates Card --- .../DataServices/UserDataService/UserDataService.cs | 1 + .../Models/SuperAdmin/SuperAdminDelegateAccount.cs | 1 + .../SuperAdmin/Delegates/SearchableDelegatesViewModel.cs | 2 ++ .../SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml | 8 ++++++++ 4 files changed, 12 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index fdc974094b..178e2f62dc 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -620,6 +620,7 @@ public void UpdateUserDetailsAccount(string firstName, string lastName, string p ce.CentreName, ce.Active AS CentreActive, da.DateRegistered, + da.LastAccessed, da.CandidateNumber, da.Approved, da.SelfReg, diff --git a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs index a18268f104..aac978b35a 100644 --- a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs @@ -19,6 +19,7 @@ public SuperAdminDelegateAccount(DelegateEntity delegateEntity) LearningHubAuthId = delegateEntity.UserAccount.LearningHubAuthId; RegistrationConfirmationHash = delegateEntity.DelegateAccount.RegistrationConfirmationHash; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; SelfReg = delegateEntity.DelegateAccount.SelfReg; Active = delegateEntity.DelegateAccount.Active; EmailVerified = delegateEntity.UserAccount.EmailVerified; diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs index 8ea0ed22e5..a615461e73 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs @@ -28,6 +28,7 @@ ReturnPageQuery returnPageQuery LearningHubID = delegates.LearningHubAuthId; AccountClaimed = delegates.RegistrationConfirmationHash; DateRegistered = delegates.DateRegistered?.ToString(Data.Helpers.DateHelper.StandardDateFormat); + LastAccessed = delegates.LastAccessed?.ToString(Data.Helpers.DateHelper.StandardDateFormat); SelRegistered = delegates.SelfReg; IsDelegateActive = delegates.Active; IsCentreEmailVerified = delegates.CentreEmailVerified == null ? false : true; @@ -52,6 +53,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public string AccountClaimed { get; set; } public string? DateRegistered { get; set; } + public string? LastAccessed { get; set; } public bool SelRegistered { get; set; } public bool IsDelegateActive { get; set; } public bool IsUserActive { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml b/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml index be45052cc3..21e74a2e1a 100644 --- a/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/SuperAdmin/Delegates/_SearchableDelegatesCard.cshtml @@ -162,6 +162,14 @@
+
+
+ Last Accessed +
+ +
+
+
Self registered From 57df4e115091324ddf6da3caaadc0dcc4621f982 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 20 Mar 2025 17:15:57 +0000 Subject: [PATCH 14/64] TD-5443-Added check for existing flag names prior to add or edit custom flags --- .../FrameworksController/Frameworks.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index 2cdda2862c..f66bc6427f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -582,14 +582,33 @@ public IActionResult EditFrameworkFlag(CustomFlagViewModel model, int frameworkI { if (ModelState.IsValid) { + var flags = frameworkService.GetCustomFlagsByFrameworkId(frameworkId, null) + .Where(fn => fn.FlagName == model.FlagName).ToList(); + + bool nameExists = flags.Any(x => x.FlagName == model.FlagName); + bool idExists = flags.Any(x => x.FlagId == flagId); + if (actionname == "Edit") { - frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists && !idExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName, model.FlagGroup, model.FlagTagClass); } else { - frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName, model.FlagGroup, model.FlagTagClass); } + return RedirectToAction("EditFrameworkFlags", "Frameworks", new { frameworkId }); } return View("Developer/EditCustomFlag", model); From 87d38416ee489dc976de938826d25a6f2bba2b2f Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Fri, 21 Mar 2025 11:35:34 +0000 Subject: [PATCH 15/64] TD-5435-The 'To Do' section has been moved above the 'Staff' section. --- .../Views/Supervisor/Index.cshtml | 164 +++++++++--------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml b/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml index db4fcd24c2..07b71ef446 100644 --- a/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Supervisor/Index.cshtml @@ -3,99 +3,99 @@ @model SupervisorDashboardViewModel; @{ - ViewData["Title"] = "Dashboard"; - ViewData["Application"] = "Supervisor"; - ViewData["HeaderPathName"] = "Supervisor"; + ViewData["Title"] = "Dashboard"; + ViewData["Application"] = "Supervisor"; + ViewData["HeaderPathName"] = "Supervisor"; } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -

Supervisor Dashboard

- @if (Model.BannerText != null) +

Supervisor Dashboard

+

Your to do list

+@if (Model.SupervisorDashboardToDoItems.Count() > 0) { -

@Model.BannerText

-} - -

Your to do list

-@if (Model.SupervisorDashboardToDoItems.Count() > 0) -{ - + else if (toDoItem.ResultsReviewRequest) + { +
  • + + Confirm self-assessment results for @toDoItem.DelegateName's @toDoItem.ProfileName + . Requested @toDoItem.Requested.ToShortDateString() +
  • + } + } + } else { -

    There are no tasks requiring your attention.

    +

    There are no tasks requiring your attention.

    +} +@if (Model.BannerText != null) +{ +

    @Model.BannerText

    } + From 9c892a45e0f14463fd27a40023bad1a20628547c Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Fri, 21 Mar 2025 14:15:33 +0000 Subject: [PATCH 16/64] TD-5436-The 'To Do' section has been moved above the 'Frameworks' section. --- .../Views/Frameworks/Index.cshtml | 149 +++++++++--------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml index 127cb973db..8bb58fd4d5 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Index.cshtml @@ -3,91 +3,92 @@ @model DashboardViewModel; @{ - ViewData["Title"] = "Dashboard"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; + ViewData["Title"] = "Dashboard"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; } @section NavMenuItems { - + } - @section NavBreadcrumbs { - -} -

    Frameworks Dashboard

    -

    Welcome @Model.Username. You are accessing as a @(Model.IsFrameworkDeveloper ? "Framework Developer" : Model.IsFrameworkContributor ? "Framework Contributor" : "Framework Viewer") and a @(Model.IsWorkforceManager ? "Workforce Manager" : Model.IsWorkforceContributor ? "Role Profile Contributor" : "Role Profile Viewer").

    - + +} +

    Frameworks Dashboard

    +

    Welcome @Model.Username. You are accessing as a @(Model.IsFrameworkDeveloper ? "Framework Developer" : Model.IsFrameworkContributor ? "Framework Contributor" : "Framework Viewer") and a @(Model.IsWorkforceManager ? "Workforce Manager" : Model.IsWorkforceContributor ? "Role Profile Contributor" : "Role Profile Viewer").

    Your to do list

    @if (Model.DashboardToDoItems.Count() > 0) { - } else { -

    There are no tasks requiring your attention.

    +

    There are no tasks requiring your attention.

    } + + From fffa334d7ee0743d616cd97e207cbb33b8658ddb Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 27 Mar 2025 14:54:10 +0000 Subject: [PATCH 17/64] TD-5438-Moved the 'View Proficiencies' button to the top. --- .../SelfAssessmentDescription.cshtml | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml index 346322f8e3..bad5f5af78 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml @@ -29,23 +29,6 @@ { } -@if (Model.IsSupervised) -{ -
    -
    - @(Html.Raw(Model.Description)) -
    -
    - -
    -
    -} -else -{ -
    @(Html.Raw(Model.Description))
    -} - @if (Model.LinearNavigation) { View @Model.VocabPlural @@ -72,6 +55,19 @@ else Continue where I left off } } - - - +@if (Model.IsSupervised) +{ +
    +
    + @(Html.Raw(Model.Description)) +
    +
    + +
    +
    +} +else +{ +
    @(Html.Raw(Model.Description))
    +} From c6bdcc31109c6cb36b88608d2e2b92b4a958f6a6 Mon Sep 17 00:00:00 2001 From: ABSINHAA Date: Thu, 27 Mar 2025 16:16:58 +0000 Subject: [PATCH 18/64] TD_5343_Adding the centre information in the footer through out the learning portal pages as texts. --- .../LearningPortal/Available/Available.cshtml | 13 +++++-------- .../LearningPortal/Completed/Completed.cshtml | 7 ++----- .../LearningPortal/Current/Current.cshtml | 7 ++----- .../Views/Shared/_DlsFooter.cshtml | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml index 5fc25aa747..a9edfdbebf 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml @@ -6,10 +6,11 @@ @{ - ViewData["Application"] = "Learning Portal"; - ViewData["Title"] = "Available Activities"; - ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Available"; - ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["Application"] = "Learning Portal"; + ViewData["Title"] = "Available Activities"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Available"; + ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["BannerText"] = Model.BannerText; } @section NavMenuItems { @@ -24,10 +25,6 @@

    Available Activities

    - @if (Model.BannerText != null) - { -

    @Model.BannerText

    - } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml index 034b347511..a1fb6041a3 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml @@ -10,6 +10,7 @@ ViewData["Title"] = "My Completed Activities"; ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Completed"; ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["BannerText"] = Model.BannerText; } @if (Model.JavascriptSearchSortFilterPaginateEnabled) @@ -19,11 +20,7 @@

    My Completed Activities

    - @if (Model.BannerText != null) - { -

    @Model.BannerText

    - } - + @if (!Model.ApiIsAccessible) {

    My Current Activities

    - @if (Model.BannerText != null) - { -

    @Model.BannerText

    - } - + @if (!Model.ApiIsAccessible) {
    @if (Model.Supervisors.Any()) { - @foreach (var supervisor in Model.Supervisors.Take(3)) - { -

    @supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)

    - } - @if (Model.Supervisors.Count() > 3) - { -

    +@(Model.Supervisors.Count() - 3) more

    - } +
    + + @{ + var supervisors = string.Join(", ", new[] { + new { Role = "Educator/Manager", Count = Model.Supervisors.Count(x => x.RoleName == "Educator/Manager") }, + new { Role = "Assessor", Count = Model.Supervisors.Count(x => x.RoleName == "Assessor") }, + new { Role = "Supervisor", Count = Model.Supervisors.Count(x => x.RoleName == "Supervisor") } + } + .Where(x => x.Count > 0) + .Select(x => $"{x.Count} {x.Role}{(x.Count > 1 ? "s" : "")}")); + } + @supervisors + +
    +
      + @foreach (var supervisor in Model.Supervisors) + { +
    • @supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)
    • + } +
    +
    +
    } else { From 3821eb400224b65d7d5b01df589dc1bef93f63c0 Mon Sep 17 00:00:00 2001 From: Auldrin Possa Date: Thu, 10 Apr 2025 12:47:39 +0100 Subject: [PATCH 25/64] TD-5511-Framework query modified- The admin userid is used to check the framework owner --- .../DataServices/FrameworkDataService.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index b3176c1bec..bdd381cfe3 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -277,13 +277,22 @@ public class FrameworkDataService : IFrameworkDataService OwnerAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers WHERE (AdminID = FW.OwnerAdminID)) AS Owner, BrandID, - CategoryID, + FW.CategoryID, TopicID, CreatedDate, PublishStatusID, UpdatedByAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers AS AdminUsers_1 WHERE (AdminID = FW.UpdatedByAdminID)) AS UpdatedBy, - CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole, + CASE + WHEN (aa.UserID = (SELECT UserID FROM AdminAccounts WHERE ID = @adminId)) THEN 3 + WHEN (fwc.CanModify = 1) OR + (SELECT COUNT(*) + FROM FrameworkCollaborators fc + JOIN AdminAccounts aa1 ON fc.AdminID = aa1.ID + WHERE fc.FrameworkID = fw.ID + AND fc.CanModify = 1 AND fc.IsDeleted = 0 + AND aa1.UserID = (SELECT aa2.UserID FROM AdminAccounts aa2 WHERE aa2.ID = 12842)) > 0 THEN 2 + WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole, fwr.ID AS FrameworkReviewID"; private const string BrandedFrameworkFields = @@ -304,9 +313,9 @@ FROM CourseTopics private const string FlagFields = @"fl.ID AS FlagId, fl.FrameworkId, fl.FlagName, fl.FlagGroup, fl.FlagTagClass"; private const string FrameworkTables = - @"Frameworks AS FW LEFT OUTER JOIN - FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 - LEFT OUTER JOIN FrameworkReviews AS fwr ON fwc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL"; + @"Frameworks AS FW INNER JOIN AdminAccounts AS aa ON aa.ID = fw.OwnerAdminID + LEFT OUTER JOIN FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 + LEFT OUTER JOIN FrameworkReviews AS fwr ON fwc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL"; private const string AssessmentQuestionFields = @"SELECT AQ.ID, AQ.Question, AQ.MinValue, AQ.MaxValue, AQ.AssessmentQuestionInputTypeID, AQI.InputTypeName, AQ.AddedByAdminId, CASE WHEN AQ.AddedByAdminId = @adminId THEN 1 ELSE 0 END AS UserIsOwner, AQ.CommentsPrompt, AQ.CommentsHint"; @@ -707,7 +716,7 @@ FROM [FrameworkCompetencies] new { competencyId, frameworkCompetencyGroupID } ); } - if(addDefaultQuestions) + if (addDefaultQuestions) { AddDefaultQuestionsToCompetency(competencyId, frameworkId); } From d1a41de01854a53a395f62d364c60669510464b8 Mon Sep 17 00:00:00 2001 From: ABSINHAA Date: Thu, 10 Apr 2025 20:50:23 +0100 Subject: [PATCH 26/64] TD_5434 code changes for the new way for showing the banner text in the footer using view component, removing the banner text from the supervisor module and adding the centre information to the footer there. --- .../DlsFooterBannerTextViewComponent.cs | 14 ++++++++++ .../DlsFooterBannerTextViewModel.cs | 13 +++++++++ .../DlsFooterBannerText/Default.cshtml | 28 +++++++++++++++++++ .../Views/Shared/_DlsFooter.cshtml | 18 ++---------- .../Views/Supervisor/Index.cshtml | 5 +--- 5 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs create mode 100644 DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml diff --git a/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs b/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs new file mode 100644 index 0000000000..cc95af7a9c --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs @@ -0,0 +1,14 @@ +using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents; +using Microsoft.AspNetCore.Mvc; + +namespace DigitalLearningSolutions.Web.ViewComponents +{ + public class DlsFooterBannerTextViewComponent : ViewComponent + { + public IViewComponentResult Invoke(string centreName) + { + var model = new DlsFooterBannerTextViewModel(centreName); + return View(model); + } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs new file mode 100644 index 0000000000..9d81f502a2 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs @@ -0,0 +1,13 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +{ + public class DlsFooterBannerTextViewModel + { + public readonly string? CentreName; + + public DlsFooterBannerTextViewModel(string centreName) + { + CentreName = centreName; + } + + } +} diff --git a/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml new file mode 100644 index 0000000000..dfb4a86629 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml @@ -0,0 +1,28 @@ +@using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +@model DlsFooterBannerTextViewModel +@{ + var currentApplication = ViewData["Application"]?.ToString(); +} +@{ + if (!String.IsNullOrEmpty(currentApplication) && (currentApplication.Contains("Learning Portal") || currentApplication.Contains("Supervisor"))) + { +

    Need help?

    + + // + } +} + +@* *@ diff --git a/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml b/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml index 313431a56f..1d2b57ed60 100644 --- a/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml +++ b/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml @@ -1,24 +1,10 @@ @{ - var currentPath = Context.Request.Path.ToString(); + }
    diff --git a/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml index 02e2a52057..d4ab3023bc 100644 --- a/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml +++ b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml @@ -1,10 +1,7 @@ @using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents @model DlsFooterBannerTextViewModel @{ - var currentApplication = ViewData["Application"]?.ToString(); -} -@{ - if (!String.IsNullOrEmpty(currentApplication) && (currentApplication.Contains("Learning Portal") || currentApplication.Contains("Supervisor"))) + if (!String.IsNullOrEmpty(Model.CentreName)) {

    Need help?