diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/LearningLogItemsDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/LearningLogItemsDataServiceTests.cs index 28d5a420a0..83b3fe00ee 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/LearningLogItemsDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/LearningLogItemsDataServiceTests.cs @@ -1,8 +1,10 @@ namespace DigitalLearningSolutions.Data.Tests.DataServices { using System; + using System.Linq; using System.Transactions; using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Data.Tests.TestHelpers; using FluentAssertions; using FluentAssertions.Execution; @@ -57,27 +59,14 @@ public void InsertLearningLogItem_inserts_expected_record() using (new AssertionScope()) { result.Should().NotBeNull(); - result!.LoggedById.Should().Be(delegateId); - result.LoggedDate.Should().Be(addedDate); - result.LinkedCompetencyLearningResourceId.Should().Be(competencyLearningResourceId); - result.ExternalUri.Should().Be(resourceLink); - result.Activity.Should().Be(resourceName); - result.ActivityType.Should().Be("Learning Hub Resource"); - result.LearningLogItemId.Should().NotBe(0); - result.IcsGuid.Should().NotBeNull(); - - result.DueDate.Should().BeNull(); - result.CompletedDate.Should().BeNull(); - result.DurationMins.Should().Be(0); - result.Outcomes.Should().BeNull(); - result.LinkedCustomisationId.Should().BeNull(); - result.VerifiedById.Should().BeNull(); - result.VerifierComments.Should().BeNull(); - result.ArchivedDate.Should().BeNull(); - result.ArchivedById.Should().BeNull(); - result.LoggedByAdminId.Should().BeNull(); - result.SeqInt.Should().BeNull(); - result.LastAccessedDate.Should().BeNull(); + AssertLearningLogItemHasCorrectValuesForLearningHubResource( + result!, + delegateId, + addedDate, + competencyLearningResourceId, + resourceName, + resourceLink + ); } } finally @@ -143,5 +132,139 @@ public void InsertLearningLogItemCompetencies_inserts_expected_record() transaction.Dispose(); } } + + [Test] + public void GetLearningLogItems_returns_all_learning_hub_resource_log_items_for_delegate() + { + // Given + const int competencyLearningResourceId = 1; + const int delegateId = 2; + const int differentDelegateId = 3; + const string firstActivityName = "activity 1"; + const string secondActivityName = "activity 2"; + const string firstResourceLink = "link 1"; + const string secondResourceLink = "link 2"; + var addedDate = new DateTime(2021, 11, 1); + + using var transaction = new TransactionScope(); + try + { + competencyLearningResourcesTestHelper.InsertCompetencyLearningResource( + 1, + competencyLearningResourceId, + 1, + 7 + ); + service.InsertLearningLogItem( + delegateId, + addedDate, + firstActivityName, + firstResourceLink, + competencyLearningResourceId + ); + service.InsertLearningLogItem( + delegateId, + addedDate, + secondActivityName, + secondResourceLink, + competencyLearningResourceId + ); + service.InsertLearningLogItem( + differentDelegateId, + addedDate, + "activity 3", + "resource link 3", + competencyLearningResourceId + ); + + // When + var result = service.GetLearningLogItems(delegateId).ToList(); + + // Then + using (new AssertionScope()) + { + result.Count.Should().Be(2); + AssertLearningLogItemHasCorrectValuesForLearningHubResource( + result[0], + delegateId, + addedDate, + competencyLearningResourceId, + firstActivityName, + firstResourceLink + ); + AssertLearningLogItemHasCorrectValuesForLearningHubResource( + result[1], + delegateId, + addedDate, + competencyLearningResourceId, + secondActivityName, + secondResourceLink + ); + } + } + finally + { + transaction.Dispose(); + } + } + + [Test] + public void UpdateLearningLogItemLastAccessedDate_should_set_date_correctly() + { + // Given + const int learningLogItemId = 2; + var testDate = new DateTime(2021, 11, 1); + + using var transaction = new TransactionScope(); + try + { + // When + service.UpdateLearningLogItemLastAccessedDate(learningLogItemId, testDate); + var result = learningLogItemsTestHelper.SelectLearningLogItemById(learningLogItemId); + + // Then + using (new AssertionScope()) + { + result.Should().NotBeNull(); + result!.LastAccessedDate.Should().Be(testDate); + } + } + finally + { + transaction.Dispose(); + } + } + + private void AssertLearningLogItemHasCorrectValuesForLearningHubResource( + LearningLogItem item, + int delegateId, + DateTime addedDate, + int competencyLearningResourceId, + string resourceName, + string resourceLink + ) + { + item.LoggedById.Should().Be(delegateId); + item.LoggedDate.Should().Be(addedDate); + item.LinkedCompetencyLearningResourceId.Should().Be(competencyLearningResourceId); + item.ExternalUri.Should().Be(resourceLink); + item.Activity.Should().Be(resourceName); + item.ActivityType.Should().Be("Learning Hub Resource"); + item.LearningLogItemId.Should().NotBe(0); + item.IcsGuid.Should().NotBeNull(); + + item.DueDate.Should().BeNull(); + item.CompletedDate.Should().BeNull(); + item.DurationMins.Should().Be(0); + item.Outcomes.Should().BeNull(); + item.LinkedCustomisationId.Should().BeNull(); + item.VerifiedById.Should().BeNull(); + item.VerifierComments.Should().BeNull(); + item.ArchivedDate.Should().BeNull(); + item.ArchivedById.Should().BeNull(); + item.LoggedByAdminId.Should().BeNull(); + item.SeqInt.Should().BeNull(); + item.LastAccessedDate.Should().BeNull(); + } } } diff --git a/DigitalLearningSolutions.Data.Tests/Services/ActionPlanServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/ActionPlanServiceTests.cs index d29d92d3ca..f5d0320469 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/ActionPlanServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/ActionPlanServiceTests.cs @@ -1,11 +1,21 @@ namespace DigitalLearningSolutions.Data.Tests.Services { using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DigitalLearningSolutions.Data.ApiClients; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; + using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Data.Models.LearningHubApiClient; using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Data.Services; using FakeItEasy; + using FizzWare.NBuilder; + using FluentAssertions; + using FluentAssertions.Execution; + using Microsoft.Extensions.Configuration; using NUnit.Framework; public class ActionPlanServiceTests @@ -13,6 +23,9 @@ public class ActionPlanServiceTests private IActionPlanService actionPlanService = null!; private IClockService clockService = null!; private ICompetencyLearningResourcesDataService competencyLearningResourcesDataService = null!; + private IConfiguration config = null!; + private Catalogue genericCatalogue = null!; + private ILearningHubApiClient learningHubApiClient = null!; private ILearningHubApiService learningHubApiService = null!; private ILearningLogItemsDataService learningLogItemsDataService = null!; private ISelfAssessmentDataService selfAssessmentDataService = null!; @@ -20,18 +33,23 @@ public class ActionPlanServiceTests [SetUp] public void Setup() { + genericCatalogue = Builder.CreateNew().With(c => c.Name = "Generic catalogue").Build(); clockService = A.Fake(); competencyLearningResourcesDataService = A.Fake(); learningLogItemsDataService = A.Fake(); learningHubApiService = A.Fake(); + learningHubApiClient = A.Fake(); selfAssessmentDataService = A.Fake(); + config = A.Fake(); actionPlanService = new ActionPlanService( competencyLearningResourcesDataService, learningLogItemsDataService, clockService, learningHubApiService, - selfAssessmentDataService + learningHubApiClient, + selfAssessmentDataService, + config ); } @@ -121,5 +139,196 @@ public void AddResourceToActionPlan_calls_expected_insert_data_service_methods() () => learningLogItemsDataService.InsertLearningLogItemCompetencies(learningLogId, A._, addedDate) ).MustHaveHappened(5, Times.Exactly); } + + [Test] + public async Task GetIncompleteActionPlanItems_returns_empty_list_if_signposting_is_disabled() + { + // Given + const int delegateId = 1; + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("false"); + + // When + var result = await actionPlanService.GetIncompleteActionPlanItems(delegateId); + + // Then + result.Should().BeEmpty(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(A._)) + .MustNotHaveHappened(); + A.CallTo(() => learningHubApiClient.GetBulkResourcesByReferenceIds(A>._)) + .MustNotHaveHappened(); + } + + [Test] + public async Task GetIncompleteActionPlanItems_returns_empty_list_if_no_incomplete_learning_log_items_found() + { + // Given + const int delegateId = 1; + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("true"); + var invalidLearningLogItems = Builder.CreateListOfSize(3) + .All().With(i => i.CompletedDate = null).And(i => i.ArchivedDate = null) + .And(i => i.LearningHubResourceReferenceId = 1) + .TheFirst(1).With(i => i.Activity = "completed").And(i => i.CompletedDate = DateTime.UtcNow) + .TheNext(1).With(i => i.Activity = "removed").And(i => i.ArchivedDate = DateTime.UtcNow) + .TheNext(1).With(i => i.Activity = "no resource link").And(i => i.LearningHubResourceReferenceId = null) + .Build(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .Returns(invalidLearningLogItems); + + // When + var result = await actionPlanService.GetIncompleteActionPlanItems(delegateId); + + // Then + result.Should().BeEmpty(); + A.CallTo(() => learningHubApiClient.GetBulkResourcesByReferenceIds(A>._)) + .MustNotHaveHappened(); + } + + [Test] + public async Task GetIncompleteActionPlanItems_returns_correctly_matched_action_plan_items() + { + // Given + const int delegateId = 1; + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("true"); + var learningLogIds = new List { 4, 5, 6, 7, 8 }; + var learningResourceIds = new List { 15, 21, 33, 48, 51 }; + var learningLogItems = Builder.CreateListOfSize(5).All() + .With(i => i.CompletedDate = null) + .And(i => i.ArchivedDate = null) + .And((i, index) => i.LearningHubResourceReferenceId = learningResourceIds[index]) + .And((i, index) => i.LearningLogItemId = learningLogIds[index]) + .Build(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .Returns(learningLogItems); + var matchedResources = Builder.CreateListOfSize(3).All() + .With((r, index) => r.RefId = learningResourceIds[index]) + .And((r, index) => r.Title = $"Title {learningResourceIds[index]}") + .And(r => r.Catalogue = genericCatalogue).Build().ToList(); + var unmatchedResourceReferences = new List + { + learningResourceIds[3], + learningResourceIds[4], + }; + var bulkReturnedItems = new BulkResourceReferences + { + ResourceReferences = matchedResources, + UnmatchedResourceReferenceIds = unmatchedResourceReferences, + }; + A.CallTo(() => learningHubApiClient.GetBulkResourcesByReferenceIds(A>._)) + .Returns(bulkReturnedItems); + + // When + var result = await actionPlanService.GetIncompleteActionPlanItems(delegateId); + + // Then + List<(int id, string title)> resultIdsAndTitles = result.Select(r => (r.Id, r.Name)).ToList(); + using (new AssertionScope()) + { + resultIdsAndTitles.Count.Should().Be(3); + resultIdsAndTitles[0].Should().Be((4, "Title 15")); + resultIdsAndTitles[1].Should().Be((5, "Title 21")); + resultIdsAndTitles[2].Should().Be((6, "Title 33")); + A.CallTo( + () => learningHubApiClient.GetBulkResourcesByReferenceIds( + A>.That.IsSameSequenceAs(learningResourceIds) + ) + ) + .MustHaveHappenedOnceExactly(); + } + } + + [Test] + public async Task GetLearningResourceLinkAndUpdateLastAccessedDate_returns_null_if_signposting_is_disabled() + { + // Given + const int learningLogItemId = 1; + const int delegateId = 2; + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("false"); + + // When + var result = + await actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, delegateId); + + // Then + result.Should().BeNull(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(A._)) + .MustNotHaveHappened(); + A.CallTo(() => learningHubApiClient.GetResourceByReferenceId(A._)) + .MustNotHaveHappened(); + } + + [Test] + public async Task + GetLearningResourceLinkAndUpdateLastAccessedDate_returns_null_if_delegate_does_not_have_active_action_plan_item_with_matching_id() + { + // Given + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("true"); + const int learningLogItemId = 1; + const int delegateId = 2; + var learningLogIds = new List { learningLogItemId, 5, 6, 7, learningLogItemId }; + var learningResourceIds = new List { 15, 21, 33, 48, null }; + var learningLogItems = Builder.CreateListOfSize(5).All() + .With(i => i.CompletedDate = null) + .And(i => i.ArchivedDate = null) + .And((i, index) => i.LearningHubResourceReferenceId = learningResourceIds[index]) + .And((i, index) => i.LearningLogItemId = learningLogIds[index]) + .TheFirst(1).With(i => i.ArchivedDate = DateTime.UtcNow) + .Build(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .Returns(learningLogItems); + + // When + var result = + await actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, delegateId); + + // Then + result.Should().BeNull(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => learningHubApiClient.GetResourceByReferenceId(A._)) + .MustNotHaveHappened(); + } + + [Test] + public async Task GetLearningResourceLinkAndUpdateLastAccessedDate_updates_last_accessed_returns_resource_link() + { + // Given + var testDate = new DateTime(2021, 12, 2); + A.CallTo(() => config[ConfigHelper.UseSignposting]).Returns("true"); + A.CallTo(() => clockService.UtcNow).Returns(testDate); + const int learningLogItemId = 1; + const int delegateId = 2; + const int resourceReferenceId = 3; + const string resourceLink = "www.test.com"; + var learningLogItems = Builder.CreateListOfSize(1).All() + .With(i => i.CompletedDate = null) + .And(i => i.ArchivedDate = null) + .And(i => i.LearningHubResourceReferenceId = resourceReferenceId) + .And(i => i.LearningLogItemId = learningLogItemId) + .Build(); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .Returns(learningLogItems); + var matchedResource = Builder.CreateNew() + .With(r => r.RefId = resourceReferenceId) + .And(r => r.Title = "Title") + .And(r => r.Catalogue = genericCatalogue) + .And(r => r.Link = resourceLink).Build(); + A.CallTo(() => learningHubApiClient.GetResourceByReferenceId(resourceReferenceId)) + .Returns(matchedResource); + + // When + var result = + await actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, delegateId); + + // Then + result.Should().Be(resourceLink); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(delegateId)) + .MustHaveHappenedOnceExactly(); + A.CallTo( + () => learningLogItemsDataService.UpdateLearningLogItemLastAccessedDate(learningLogItemId, testDate) + ) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => learningHubApiClient.GetResourceByReferenceId(resourceReferenceId)) + .MustHaveHappenedOnceExactly(); + } } } diff --git a/DigitalLearningSolutions.Data.Tests/TestHelpers/LearningLogItemsTestHelper.cs b/DigitalLearningSolutions.Data.Tests/TestHelpers/LearningLogItemsTestHelper.cs index 3217f932f8..56442f23f3 100644 --- a/DigitalLearningSolutions.Data.Tests/TestHelpers/LearningLogItemsTestHelper.cs +++ b/DigitalLearningSolutions.Data.Tests/TestHelpers/LearningLogItemsTestHelper.cs @@ -45,6 +45,37 @@ FROM LearningLogItems l ); } + public LearningLogItem? SelectLearningLogItemById(int id) + { + return connection.QuerySingleOrDefault( + @"SELECT + LearningLogItemID, + LoggedDate, + LoggedByID, + DueDate, + CompletedDate, + DurationMins, + Activity, + Outcomes, + LinkedCustomisationID, + VerifiedByID, + VerifierComments, + ArchivedDate, + ArchivedByID, + ICSGUID, + LoggedByAdminID, + TypeLabel AS ActivityType, + ExternalUri, + SeqInt, + LastAccessedDate, + LinkedCompetencyLearningResourceID + FROM LearningLogItems l + INNER JOIN ActivityTypes a ON a.ID = l.ActivityTypeID + WHERE LearningLogItemID = @id", + new { id } + ); + } + public CandidateAssessmentLearningLogItem? SelectCandidateAssessmentLearningLogItem() { return connection.QuerySingleOrDefault( diff --git a/DigitalLearningSolutions.Data/DataServices/LearningLogItemsDataService.cs b/DigitalLearningSolutions.Data/DataServices/LearningLogItemsDataService.cs index aa7dd27eec..7a433e7ef5 100644 --- a/DigitalLearningSolutions.Data/DataServices/LearningLogItemsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/LearningLogItemsDataService.cs @@ -1,11 +1,15 @@ namespace DigitalLearningSolutions.Data.DataServices { using System; + using System.Collections.Generic; using System.Data; using Dapper; + using DigitalLearningSolutions.Data.Models.LearningResources; public interface ILearningLogItemsDataService { + IEnumerable GetLearningLogItems(int delegateId); + int InsertLearningLogItem( int delegateId, DateTime addedDate, @@ -17,10 +21,13 @@ int competencyLearningResourceId void InsertCandidateAssessmentLearningLogItem(int assessmentId, int learningLogId); void InsertLearningLogItemCompetencies(int learningLogId, int competencyId, DateTime associatedDate); + + void UpdateLearningLogItemLastAccessedDate(int id, DateTime lastAccessedDate); } public class LearningLogItemsDataService : ILearningLogItemsDataService { + private const string LearningHubResourceActivityLabel = "Learning Hub Resource"; private readonly IDbConnection connection; public LearningLogItemsDataService(IDbConnection connection) @@ -28,6 +35,40 @@ public LearningLogItemsDataService(IDbConnection connection) this.connection = connection; } + public IEnumerable GetLearningLogItems(int delegateId) + { + return connection.Query( + $@"SELECT + LearningLogItemID, + LoggedDate, + LoggedByID, + DueDate, + CompletedDate, + DurationMins, + Activity, + Outcomes, + LinkedCustomisationID, + VerifiedByID, + VerifierComments, + ArchivedDate, + ArchivedByID, + ICSGUID, + LoggedByAdminID, + TypeLabel AS ActivityType, + ExternalUri, + SeqInt, + LastAccessedDate, + LinkedCompetencyLearningResourceID, + clr.LHResourceReferenceID AS LearningHubResourceReferenceID + FROM LearningLogItems l + INNER JOIN ActivityTypes a ON a.ID = l.ActivityTypeID + INNER JOIN CompetencyLearningResources AS clr ON clr.ID = l.LinkedCompetencyLearningResourceID + WHERE LoggedById = @delegateId + AND a.TypeLabel = '{LearningHubResourceActivityLabel}'", + new { delegateId } + ); + } + public int InsertLearningLogItem( int delegateId, DateTime addedDate, @@ -103,5 +144,15 @@ public void InsertLearningLogItemCompetencies(int learningLogId, int competencyI new { learningLogId, competencyId, associatedDate } ); } + + public void UpdateLearningLogItemLastAccessedDate(int id, DateTime lastAccessedDate) + { + connection.Execute( + @"UPDATE LearningLogItems + SET LastAccessedDate = @lastAccessedDate + WHERE LearningLogItemID = @id", + new { id, lastAccessedDate } + ); + } } } diff --git a/DigitalLearningSolutions.Data/Helpers/ConfigHelper.cs b/DigitalLearningSolutions.Data/Helpers/ConfigHelper.cs index dc8aec0d1a..a1545970d9 100644 --- a/DigitalLearningSolutions.Data/Helpers/ConfigHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/ConfigHelper.cs @@ -8,7 +8,7 @@ public static class ConfigHelper public const string CurrentSystemBaseUrlName = "CurrentSystemBaseUrl"; private const string LearningHubOpenApiKey = "LearningHubOpenAPIKey"; private const string LearningHubOpenApiBaseUrl = "LearningHubOpenAPIBaseUrl"; - private const string UseSignposting = "FeatureManagement:UseSignposting"; + public const string UseSignposting = "FeatureManagement:UseSignposting"; public static string GetAppRootPath(this IConfiguration config) { diff --git a/DigitalLearningSolutions.Data/Models/LearningResources/ActionPlanItem.cs b/DigitalLearningSolutions.Data/Models/LearningResources/ActionPlanItem.cs new file mode 100644 index 0000000000..fcc14a4b11 --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/LearningResources/ActionPlanItem.cs @@ -0,0 +1,32 @@ +namespace DigitalLearningSolutions.Data.Models.LearningResources +{ + using System; + using DigitalLearningSolutions.Data.Models.LearningHubApiClient; + + public class ActionPlanItem : CurrentLearningItem + { + public ActionPlanItem() { } + + public ActionPlanItem(LearningLogItem learningLogItem, ResourceReferenceWithResourceDetails resource) + { + Name = resource.Title; + Id = learningLogItem.LearningLogItemId; + StartedDate = learningLogItem.LoggedDate; + CompleteByDate = learningLogItem.DueDate; + Completed = learningLogItem.CompletedDate; + RemovedDate = learningLogItem.ArchivedDate; + LastAccessed = learningLogItem.LastAccessedDate; + ResourceDescription = resource.Description; + ResourceType = resource.ResourceType; + CatalogueName = resource.Catalogue.Name; + ResourceLink = resource.Link; + } + + public DateTime? Completed { get; set; } + public DateTime? RemovedDate { get; set; } + public string ResourceDescription { get; set; } + public string ResourceLink { get; set; } + public string CatalogueName { get; set; } + public string ResourceType { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/LearningResources/LearningLogItem.cs b/DigitalLearningSolutions.Data/Models/LearningResources/LearningLogItem.cs index ee7509229c..5e5b9237b9 100644 --- a/DigitalLearningSolutions.Data/Models/LearningResources/LearningLogItem.cs +++ b/DigitalLearningSolutions.Data/Models/LearningResources/LearningLogItem.cs @@ -25,5 +25,6 @@ public class LearningLogItem public int? LoggedByAdminId { get; set; } public int? SeqInt { get; set; } public DateTime? LastAccessedDate { get; set; } + public int? LearningHubResourceReferenceId { get; set; } } } diff --git a/DigitalLearningSolutions.Data/Services/ActionPlanService.cs b/DigitalLearningSolutions.Data/Services/ActionPlanService.cs index 50353983a6..e1f9a02e08 100644 --- a/DigitalLearningSolutions.Data/Services/ActionPlanService.cs +++ b/DigitalLearningSolutions.Data/Services/ActionPlanService.cs @@ -1,19 +1,31 @@ namespace DigitalLearningSolutions.Data.Services { + using System.Collections.Generic; using System.Linq; + using System.Threading.Tasks; using System.Transactions; + using DigitalLearningSolutions.Data.ApiClients; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; + using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Data.Models.LearningResources; + using Microsoft.Extensions.Configuration; public interface IActionPlanService { void AddResourceToActionPlan(int competencyLearningResourceId, int delegateId, int selfAssessmentId); + + Task> GetIncompleteActionPlanItems(int delegateId); + + Task GetLearningResourceLinkAndUpdateLastAccessedDate(int learningLogItemId, int delegateId); } public class ActionPlanService : IActionPlanService { private readonly IClockService clockService; private readonly ICompetencyLearningResourcesDataService competencyLearningResourcesDataService; + private readonly IConfiguration config; + private readonly ILearningHubApiClient learningHubApiClient; private readonly ILearningHubApiService learningHubApiService; private readonly ILearningLogItemsDataService learningLogItemsDataService; private readonly ISelfAssessmentDataService selfAssessmentDataService; @@ -23,14 +35,18 @@ public ActionPlanService( ILearningLogItemsDataService learningLogItemsDataService, IClockService clockService, ILearningHubApiService learningHubApiService, - ISelfAssessmentDataService selfAssessmentDataService + ILearningHubApiClient learningHubApiClient, + ISelfAssessmentDataService selfAssessmentDataService, + IConfiguration config ) { this.competencyLearningResourcesDataService = competencyLearningResourcesDataService; this.learningLogItemsDataService = learningLogItemsDataService; this.clockService = clockService; this.learningHubApiService = learningHubApiService; + this.learningHubApiClient = learningHubApiClient; this.selfAssessmentDataService = selfAssessmentDataService; + this.config = config; } public void AddResourceToActionPlan(int competencyLearningResourceId, int delegateId, int selfAssessmentId) @@ -68,10 +84,71 @@ public void AddResourceToActionPlan(int competencyLearningResourceId, int delega foreach (var competencyId in learningLogCompetenciesToAdd) { - learningLogItemsDataService.InsertLearningLogItemCompetencies(learningLogItemId, competencyId, addedDate); + learningLogItemsDataService.InsertLearningLogItemCompetencies( + learningLogItemId, + competencyId, + addedDate + ); } transaction.Complete(); } + + public async Task> GetIncompleteActionPlanItems(int delegateId) + { + if (!config.IsSignpostingUsed()) + { + return new List(); + } + + var incompleteLearningLogItems = learningLogItemsDataService.GetLearningLogItems(delegateId) + .Where( + i => i.CompletedDate == null && i.ArchivedDate == null && i.LearningHubResourceReferenceId != null + ).ToList(); + + if (!incompleteLearningLogItems.Any()) + { + return new List(); + } + + var incompleteResourceIds = incompleteLearningLogItems.Select(i => i.LearningHubResourceReferenceId!.Value); + var bulkResponse = await learningHubApiClient.GetBulkResourcesByReferenceIds(incompleteResourceIds); + var incompleteActionPlanItems = bulkResponse.ResourceReferences.Select( + resource => new ActionPlanItem( + incompleteLearningLogItems.Single(i => i.LearningHubResourceReferenceId!.Value == resource.RefId), + resource + ) + ); + return incompleteActionPlanItems; + } + + public async Task GetLearningResourceLinkAndUpdateLastAccessedDate(int learningLogItemId, int delegateId) + { + if (!config.IsSignpostingUsed()) + { + return null; + } + + var actionPlanItem = learningLogItemsDataService.GetLearningLogItems(delegateId) + .SingleOrDefault( + i => i.LearningLogItemId == learningLogItemId + && i.LearningHubResourceReferenceId != null + && i.ArchivedDate == null + ); + + if (actionPlanItem == null) + { + return null; + } + + learningLogItemsDataService.UpdateLearningLogItemLastAccessedDate(learningLogItemId, clockService.UtcNow); + + var resource = + await learningHubApiClient.GetResourceByReferenceId( + actionPlanItem.LearningHubResourceReferenceId!.Value + ); + + return resource.Link; + } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/CurrentTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/CurrentTests.cs index 64e983b7ee..5116c2d131 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/CurrentTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/CurrentTests.cs @@ -1,10 +1,14 @@ namespace DigitalLearningSolutions.Web.Tests.Controllers.LearningPortal { using System; + using System.Linq; + using System.Threading.Tasks; using DigitalLearningSolutions.Data.Enums; + using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Web.Tests.TestHelpers; using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current; using FakeItEasy; + using FizzWare.NBuilder; using FluentAssertions; using FluentAssertions.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc; @@ -13,27 +17,29 @@ public partial class LearningPortalControllerTests { [Test] - public void Current_action_should_return_view_result() + public async Task Current_action_should_return_view_result() { // Given var currentCourses = new[] { CurrentCourseHelper.CreateDefaultCurrentCourse(), - CurrentCourseHelper.CreateDefaultCurrentCourse() + CurrentCourseHelper.CreateDefaultCurrentCourse(), }; var selfAssessments = new[] { SelfAssessmentHelper.CreateDefaultSelfAssessment(), SelfAssessmentHelper.CreateDefaultSelfAssessment(), }; - + var actionPlanItems = Builder.CreateListOfSize(2).Build().ToArray(); + var bannerText = "bannerText"; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); A.CallTo(() => selfAssessmentService.GetSelfAssessmentsForCandidate(CandidateId)).Returns(selfAssessments); + A.CallTo(() => actionPlanService.GetIncompleteActionPlanItems(CandidateId)).Returns(actionPlanItems); A.CallTo(() => centresDataService.GetBannerText(CentreId)).Returns(bannerText); // When - var result = controller.Current(); + var result = await controller.Current(); // Then var expectedModel = new CurrentPageViewModel( @@ -42,6 +48,7 @@ public void Current_action_should_return_view_result() "LastAccessed", "Descending", selfAssessments, + actionPlanItems, bannerText, 1 ); @@ -53,10 +60,13 @@ public void Current_action_should_return_view_result() public void Trying_to_edit_complete_by_date_when_not_self_enrolled_should_return_403() { // Given - var currentCourse = CurrentCourseHelper.CreateDefaultCurrentCourse(enrollmentMethodId: 0, completeByDate: new DateTime(2020, 1, 1)); + var currentCourse = CurrentCourseHelper.CreateDefaultCurrentCourse( + enrollmentMethodId: 0, + completeByDate: new DateTime(2020, 1, 1) + ); var currentCourses = new[] { - currentCourse + currentCourse, }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -77,7 +87,7 @@ public void Trying_to_edit_complete_by_date_for_non_existent_course_should_retur // Given var currentCourses = new[] { - CurrentCourseHelper.CreateDefaultCurrentCourse(2) + CurrentCourseHelper.CreateDefaultCurrentCourse(2), }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -169,7 +179,8 @@ public void Removing_a_current_course_should_call_the_course_service() controller.RemoveCurrentCourse(1); // Then - A.CallTo(() => courseDataService.RemoveCurrentCourse(1, CandidateId, RemovalMethod.RemovedByDelegate)).MustHaveHappened(); + A.CallTo(() => courseDataService.RemoveCurrentCourse(1, CandidateId, RemovalMethod.RemovedByDelegate)) + .MustHaveHappened(); } [Test] @@ -180,7 +191,7 @@ public void Remove_confirmation_for_a_current_course_should_show_confirmation() var currentCourse = CurrentCourseHelper.CreateDefaultCurrentCourse(customisationId); var currentCourses = new[] { - currentCourse + currentCourse, }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -199,7 +210,7 @@ public void Removing_non_existent_course_should_return_404() // Given var currentCourses = new[] { - CurrentCourseHelper.CreateDefaultCurrentCourse(2) + CurrentCourseHelper.CreateDefaultCurrentCourse(2), }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -221,7 +232,7 @@ public void Requesting_a_course_unlock_should_call_the_unlock_service() const int progressId = 1; var currentCourses = new[] { - CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: progressId, locked: true) + CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: progressId, locked: true), }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -238,7 +249,7 @@ public void Requesting_unlock_for_non_existent_course_should_return_404() // Given var currentCourses = new[] { - CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: 2, locked: true) + CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: 2, locked: true), }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -260,7 +271,7 @@ public void Requesting_unlock_for_unlocked_course_should_return_404() const int progressId = 2; var currentCourses = new[] { - CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: progressId) + CurrentCourseHelper.CreateDefaultCurrentCourse(progressId: progressId), }; A.CallTo(() => courseDataService.GetCurrentCourses(CandidateId)).Returns(currentCourses); @@ -276,17 +287,52 @@ public void Requesting_unlock_for_unlocked_course_should_return_404() } [Test] - public void Current_action_should_have_banner_text() + public async Task Current_action_should_have_banner_text() { // Given const string bannerText = "Banner text"; A.CallTo(() => centresDataService.GetBannerText(CentreId)).Returns(bannerText); // When - var currentViewModel = CurrentCourseHelper.CurrentPageViewModelFromController(controller); + var currentViewModel = await CurrentCourseHelper.CurrentPageViewModelFromController(controller); // Then currentViewModel.BannerText.Should().Be(bannerText); } + + [Test] + public async Task LaunchLearningResource_should_redirect_to_not_found_if_link_cannot_be_retrieved() + { + // Given + const int learningLogItemId = 1; + A.CallTo(() => actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, 11)) + .Returns((string?)null); + + // When + var result = await controller.LaunchLearningResource(learningLogItemId); + + // Then + result.Should().BeNotFoundResult(); + A.CallTo(() => actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, 11)) + .MustHaveHappenedOnceExactly(); + } + + [Test] + public async Task LaunchLearningResource_should_redirect_to_returned_link() + { + // Given + const int learningLogItemId = 1; + const string resourceLink = "www.resource.com"; + A.CallTo(() => actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, 11)) + .Returns(resourceLink); + + // When + var result = await controller.LaunchLearningResource(learningLogItemId); + + // Then + result.Should().BeRedirectResult().WithUrl(resourceLink); + A.CallTo(() => actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, 11)) + .MustHaveHappenedOnceExactly(); + } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/LearningPortalControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/LearningPortalControllerTests.cs index 8fbf0e8bc9..176d58e77e 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/LearningPortalControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/LearningPortalControllerTests.cs @@ -15,6 +15,7 @@ namespace DigitalLearningSolutions.Web.Tests.Controllers.LearningPortal public partial class LearningPortalControllerTests { private LearningPortalController controller = null!; + private IActionPlanService actionPlanService = null!; private ICentresDataService centresDataService = null!; private ICourseDataService courseDataService = null!; private ISelfAssessmentService selfAssessmentService = null!; @@ -32,6 +33,7 @@ public partial class LearningPortalControllerTests [SetUp] public void SetUp() { + actionPlanService = A.Fake(); centresDataService = A.Fake(); courseDataService = A.Fake(); selfAssessmentService = A.Fake(); @@ -58,7 +60,8 @@ public void SetUp() frameworkNotificationService, logger, config, - filteredApiHelperService + filteredApiHelperService, + actionPlanService ) { ControllerContext = new ControllerContext() { HttpContext = new DefaultHttpContext { User = user } } diff --git a/DigitalLearningSolutions.Web.Tests/TestHelpers/CurrentCourseHelper.cs b/DigitalLearningSolutions.Web.Tests/TestHelpers/CurrentCourseHelper.cs index 971d16e3a7..e97bd56960 100644 --- a/DigitalLearningSolutions.Web.Tests/TestHelpers/CurrentCourseHelper.cs +++ b/DigitalLearningSolutions.Web.Tests/TestHelpers/CurrentCourseHelper.cs @@ -1,6 +1,7 @@ namespace DigitalLearningSolutions.Web.Tests.TestHelpers { using System; + using System.Threading.Tasks; using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Web.Controllers.LearningPortalController; using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current; @@ -48,9 +49,9 @@ public static CurrentCourse CreateDefaultCurrentCourse( }; } - public static CurrentPageViewModel CurrentPageViewModelFromController(LearningPortalController controller) + public static async Task CurrentPageViewModelFromController(LearningPortalController controller) { - var result = controller.Current() as ViewResult; + var result = await controller.Current() as ViewResult; return (CurrentPageViewModel)result!.Model; } } diff --git a/DigitalLearningSolutions.Web.Tests/ViewModels/LearningPortal/CurrentPageViewModelTests.cs b/DigitalLearningSolutions.Web.Tests/ViewModels/LearningPortal/CurrentPageViewModelTests.cs index 42f8c00a26..73e7a65a7d 100644 --- a/DigitalLearningSolutions.Web.Tests/ViewModels/LearningPortal/CurrentPageViewModelTests.cs +++ b/DigitalLearningSolutions.Web.Tests/ViewModels/LearningPortal/CurrentPageViewModelTests.cs @@ -4,8 +4,10 @@ namespace DigitalLearningSolutions.Web.Tests.ViewModels.LearningPortal using System.Linq; using DigitalLearningSolutions.Data.Models.SelfAssessments; using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Web.Tests.TestHelpers; using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current; + using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -14,6 +16,7 @@ public class CurrentPageViewModelTests private CurrentPageViewModel model = null!; private CurrentCourse[] currentCourses = null!; private SelfAssessment[] selfAssessments = null!; + private ActionPlanItem[] actionPlanItems = null!; [SetUp] public void SetUp() @@ -84,12 +87,15 @@ public void SetUp() Description = "Self Assessment 2 Description" }, }; + actionPlanItems = Builder.CreateListOfSize(2).Build().ToArray(); + model = new CurrentPageViewModel( currentCourses, null, "Name", "Ascending", selfAssessments, + actionPlanItems, null, 1 ); @@ -159,7 +165,7 @@ public void Current_courses_should_map_to_view_models_in_the_correct_order( bool expectedIsSupervisor, bool expectedIsGroup) { - var course = model.CurrentCourses.ElementAt(index) as CurrentCourseViewModel; + var course = model.CurrentActivities.ElementAt(index) as CurrentCourseViewModel; course!.Id.Should().Be(expectedId); course.Name.Should().Be(expectedName); course.HasDiagnosticAssessment.Should().Be(expectedDiagnostic); @@ -197,18 +203,20 @@ public void Current_courses_should_default_to_returning_the_first_ten_courses() SelfAssessmentHelper.CreateDefaultSelfAssessment(), SelfAssessmentHelper.CreateDefaultSelfAssessment(), }; + model = new CurrentPageViewModel( courses, null, "Name", "Ascending", selfAssessments, + actionPlanItems, null, 1 ); - model.CurrentCourses.Count().Should().Be(10); - model.CurrentCourses.FirstOrDefault(course => course.Name == "k course 11").Should().BeNull(); + model.CurrentActivities.Count().Should().Be(10); + model.CurrentActivities.FirstOrDefault(course => course.Name == "k course 11").Should().BeNull(); } [Test] @@ -233,6 +241,7 @@ public void Current_courses_should_correctly_return_the_second_page_of_courses() SelfAssessmentHelper.CreateDefaultSelfAssessment(), SelfAssessmentHelper.CreateDefaultSelfAssessment(), }; + actionPlanItems = Builder.CreateListOfSize(2).Build().ToArray(); model = new CurrentPageViewModel( courses, @@ -240,12 +249,13 @@ public void Current_courses_should_correctly_return_the_second_page_of_courses() "Name", "Ascending", selfAssessments, + actionPlanItems, null, 2 ); - model.CurrentCourses.Count().Should().Be(3); - model.CurrentCourses.First().Name.Should().Be("k course 11"); + model.CurrentActivities.Count().Should().Be(5); + model.CurrentActivities.First().Name.Should().Be("k course 11"); } [Test] @@ -276,6 +286,7 @@ public void Current_courses_should_return_correct_number_of_search_results() "Name", "Ascending", selfAssessments, + actionPlanItems, null, 1 ); diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Current.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Current.cs index 444b84c245..d37d591def 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Current.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Current.cs @@ -2,6 +2,7 @@ { using System; using System.Linq; + using System.Threading.Tasks; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; @@ -12,7 +13,7 @@ public partial class LearningPortalController { [Route("/LearningPortal/Current/{page=1:int}")] - public IActionResult Current( + public async Task Current( string? searchString = null, string? sortBy = null, string sortDirection = BaseSearchablePageViewModel.Descending, @@ -20,29 +21,33 @@ public IActionResult Current( ) { sortBy ??= CourseSortByOptions.LastAccessed.PropertyName; - - var currentCourses = courseDataService.GetCurrentCourses(User.GetCandidateIdKnownNotNull()); + var delegateId = User.GetCandidateIdKnownNotNull(); + var currentCourses = courseDataService.GetCurrentCourses(delegateId); var bannerText = GetBannerText(); var selfAssessments = - selfAssessmentService.GetSelfAssessmentsForCandidate(User.GetCandidateIdKnownNotNull()); + selfAssessmentService.GetSelfAssessmentsForCandidate(delegateId); + var learningResources = await actionPlanService.GetIncompleteActionPlanItems(delegateId); var model = new CurrentPageViewModel( currentCourses, searchString, sortBy, sortDirection, selfAssessments, + learningResources, bannerText, page ); return View("Current/Current", model); } - public IActionResult AllCurrentItems() + public async Task AllCurrentItems() { - var currentCourses = courseDataService.GetCurrentCourses(User.GetCandidateIdKnownNotNull()); + var delegateId = User.GetCandidateIdKnownNotNull(); + var currentCourses = courseDataService.GetCurrentCourses(delegateId); var selfAssessment = - selfAssessmentService.GetSelfAssessmentsForCandidate(User.GetCandidateIdKnownNotNull()); - var model = new AllCurrentItemsPageViewModel(currentCourses, selfAssessment); + selfAssessmentService.GetSelfAssessmentsForCandidate(delegateId); + var learningResources = await actionPlanService.GetIncompleteActionPlanItems(delegateId); + var model = new AllCurrentItemsPageViewModel(currentCourses, selfAssessment, learningResources); return View("Current/AllCurrentItems", model); } @@ -119,7 +124,11 @@ public IActionResult RemoveCurrentCourseConfirmation(int id) [HttpPost] public IActionResult RemoveCurrentCourse(int progressId) { - courseDataService.RemoveCurrentCourse(progressId, User.GetCandidateIdKnownNotNull(), RemovalMethod.RemovedByDelegate); + courseDataService.RemoveCurrentCourse( + progressId, + User.GetCandidateIdKnownNotNull(), + RemovalMethod.RemovedByDelegate + ); return RedirectToAction("Current"); } @@ -140,5 +149,21 @@ public IActionResult RequestUnlock(int progressId) notificationService.SendUnlockRequest(progressId); return View("Current/UnlockCurrentCourse"); } + + [Route("/LearningPortal/Current/LaunchLearningResource/{learningLogItemId}")] + public async Task LaunchLearningResource(int learningLogItemId) + { + var delegateId = User.GetCandidateIdKnownNotNull(); + var learningResourceLink = + await actionPlanService.GetLearningResourceLinkAndUpdateLastAccessedDate(learningLogItemId, delegateId); + + if (string.IsNullOrWhiteSpace(learningResourceLink)) + { + return NotFound(); + } + + // TODO: HEEDLS-678 redirect user to new LH forwarding endpoint. + return Redirect(learningResourceLink); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/LearningPortalController.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/LearningPortalController.cs index ac6d2ebc51..2a8fba0717 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/LearningPortalController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/LearningPortalController.cs @@ -14,6 +14,7 @@ namespace DigitalLearningSolutions.Web.Controllers.LearningPortalController [Authorize(Policy = CustomPolicies.UserOnly)] public partial class LearningPortalController : Controller { + private readonly IActionPlanService actionPlanService; private readonly ICentresDataService centresDataService; private readonly IConfiguration config; private readonly ICourseDataService courseDataService; @@ -33,7 +34,8 @@ public LearningPortalController( IFrameworkNotificationService frameworkNotificationService, ILogger logger, IConfiguration config, - IFilteredApiHelperService filteredApiHelperService + IFilteredApiHelperService filteredApiHelperService, + IActionPlanService actionPlanService ) { this.centresDataService = centresDataService; @@ -45,6 +47,7 @@ IFilteredApiHelperService filteredApiHelperService this.logger = logger; this.config = config; this.filteredApiHelperService = filteredApiHelperService; + this.actionPlanService = actionPlanService; } [SetDlsSubApplication(nameof(DlsSubApplication.LearningPortal))] diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/AllCurrentItemsPageViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/AllCurrentItemsPageViewModel.cs index 428eae0021..bfc129f882 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/AllCurrentItemsPageViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/AllCurrentItemsPageViewModel.cs @@ -4,23 +4,29 @@ using System.Linq; using DigitalLearningSolutions.Data.Models.SelfAssessments; using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments; public class AllCurrentItemsPageViewModel { - public readonly IEnumerable CurrentCourses; - public readonly IEnumerable SelfAssessments; + public readonly IEnumerable CurrentItems; public AllCurrentItemsPageViewModel( IEnumerable currentCourses, - IEnumerable selfAssessments + IEnumerable selfAssessments, + IEnumerable actionPlanItems ) { - CurrentCourses = currentCourses.Select(course => new CurrentCourseViewModel(course)); + CurrentItems = currentCourses.Select(course => new CurrentCourseViewModel(course)); foreach (SelfAssessment selfAssessment in selfAssessments) { - CurrentCourses = CurrentCourses.Append(new SelfAssessmentCardViewModel(selfAssessment)); - }; + CurrentItems = CurrentItems.Append(new SelfAssessmentCardViewModel(selfAssessment)); + } + + foreach (var actionPlanItem in actionPlanItems) + { + CurrentItems = CurrentItems.Append(new LearningResourceCardViewModel(actionPlanItem)); + } } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentLearningItemViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentLearningItemViewModel.cs index 66fd24eed1..90eae41788 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentLearningItemViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentLearningItemViewModel.cs @@ -6,9 +6,6 @@ public class CurrentLearningItemViewModel : StartedLearningItemViewModel { - public DateTime? CompleteByDate { get; } - public OldDateValidator.ValidationResult? CompleteByValidationResult { get; set; } - public CurrentLearningItemViewModel( CurrentLearningItem course ) : base(course) @@ -16,6 +13,9 @@ CurrentLearningItem course CompleteByDate = course.CompleteByDate; } + public DateTime? CompleteByDate { get; } + public OldDateValidator.ValidationResult? CompleteByValidationResult { get; set; } + public string DateStyle() { if (CompleteByDate < DateTime.Today) @@ -23,7 +23,7 @@ public string DateStyle() return "overdue"; } - if (CompleteByDate < (DateTime.Today + TimeSpan.FromDays(30))) + if (CompleteByDate < DateTime.Today + TimeSpan.FromDays(30)) { return "due-soon"; } @@ -34,11 +34,11 @@ public string DateStyle() public string DueByDescription() { return DateStyle() switch - { - "overdue" => "Course overdue; ", - "due-soon" => "Course due soon; ", - _ => "" - }; + { + "overdue" => "Activity overdue; ", + "due-soon" => "Activity due soon; ", + _ => "", + }; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentPageViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentPageViewModel.cs index be4c82fb18..eaccf7a548 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentPageViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/CurrentPageViewModel.cs @@ -4,6 +4,7 @@ namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current using System.Linq; using DigitalLearningSolutions.Data.Models; using DigitalLearningSolutions.Data.Models.Courses; + using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Data.Models.SelfAssessments; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; @@ -19,16 +20,15 @@ public CurrentPageViewModel( string sortBy, string sortDirection, IEnumerable selfAssessments, + IEnumerable actionPlanItems, string? bannerText, int page ) : base(searchString, page, false, sortBy, sortDirection, searchLabel: "Search your current courses") { BannerText = bannerText; var allItems = currentCourses.Cast().ToList(); - foreach (SelfAssessment selfAssessment in selfAssessments) - { - allItems.Add(selfAssessment); - } + allItems.AddRange(selfAssessments); + allItems.AddRange(actionPlanItems); var sortedItems = GenericSortingHelper.SortAllItems( allItems.AsQueryable(), @@ -40,20 +40,20 @@ int page SetTotalPages(); var paginatedItems = GetItemsOnCurrentPage(filteredItems); - CurrentCourses = paginatedItems.Select( - course => + CurrentActivities = paginatedItems.Select( + activity => { - if (course is CurrentCourse currentCourse) + return activity switch { - return new CurrentCourseViewModel(currentCourse); - } - - return new SelfAssessmentCardViewModel((SelfAssessment)course); + CurrentCourse currentCourse => new CurrentCourseViewModel(currentCourse), + SelfAssessment selfAssessment => new SelfAssessmentCardViewModel(selfAssessment), + _ => new LearningResourceCardViewModel((ActionPlanItem)activity) + }; } ); } - public IEnumerable CurrentCourses { get; } + public IEnumerable CurrentActivities { get; } public override IEnumerable<(string, string)> SortOptions { get; } = new[] { @@ -65,6 +65,6 @@ int page CourseSortByOptions.PassedSections }; - public override bool NoDataFound => !CurrentCourses.Any() && NoSearchOrFilter; + public override bool NoDataFound => !CurrentActivities.Any() && NoSearchOrFilter; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/LearningResourceCardViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/LearningResourceCardViewModel.cs new file mode 100644 index 0000000000..d8580ede43 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Current/LearningResourceCardViewModel.cs @@ -0,0 +1,20 @@ +namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current +{ + using DigitalLearningSolutions.Data.Models.LearningResources; + + public class LearningResourceCardViewModel : CurrentLearningItemViewModel + { + public LearningResourceCardViewModel(ActionPlanItem item) : base(item) + { + LaunchResourceLink = item.ResourceLink; + ResourceDescription = item.ResourceDescription; + CatalogueName = item.CatalogueName; + ResourceType = item.ResourceType; + } + + public string LaunchResourceLink { get; set; } + public string ResourceDescription { get; set; } + public string CatalogueName { get; set; } + public string ResourceType { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/AllCurrentItems.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/AllCurrentItems.cshtml index c508ea5018..fcab2a4683 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/AllCurrentItems.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/AllCurrentItems.cshtml @@ -15,14 +15,12 @@ -@foreach (var currentLearningItem in Model.CurrentCourses) -{ - if (currentLearningItem is SelfAssessmentCardViewModel) - { +@foreach (var currentLearningItem in Model.CurrentItems) { + if (currentLearningItem is SelfAssessmentCardViewModel) { - } - else - { + } else if (currentLearningItem is LearningResourceCardViewModel) { + + } else { } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml index be80ad6f75..731ac3a615 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml @@ -20,8 +20,7 @@

My Current Activities

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

@Model.BannerText

} @@ -36,9 +35,11 @@
- @foreach (var currentLearningItem in Model.CurrentCourses) { + @foreach (var currentLearningItem in Model.CurrentActivities) { if (currentLearningItem is SelfAssessmentCardViewModel) { + } else if (currentLearningItem is LearningResourceCardViewModel) { + } else { } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/CurrentCourseCard/_CurrentCourseCard.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/CurrentCourseCard/_CurrentCourseCard.cshtml index c0aee59319..28ae491941 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/CurrentCourseCard/_CurrentCourseCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/CurrentCourseCard/_CurrentCourseCard.cshtml @@ -1,11 +1,15 @@ @using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current @model CurrentCourseViewModel -
+
@Model.DueByDescription() - + @Model.Name @@ -20,9 +24,11 @@
- @if (Model.SelfEnrolled) - { - + @if (Model.SelfEnrolled) { + Remove course } @@ -32,8 +38,8 @@
diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/_CurrentLearningResourceCard.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/_CurrentLearningResourceCard.cshtml new file mode 100644 index 0000000000..0b429f1307 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/_CurrentLearningResourceCard.cshtml @@ -0,0 +1,54 @@ +@using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current +@model LearningResourceCardViewModel + + diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_SelfAssessmentCard.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_SelfAssessmentCard.cshtml index b078aa5057..99b05e68ad 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_SelfAssessmentCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_SelfAssessmentCard.cshtml @@ -5,7 +5,11 @@
@Model.DueByDescription() - + @Model.Name @@ -22,7 +26,10 @@
diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_Dates.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_Dates.cshtml index c329e3f094..0a5671da2b 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_Dates.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_Dates.cshtml @@ -48,6 +48,16 @@
} + else if (currentLearningItemViewModel is LearningResourceCardViewModel) + { + + }
} @if (Model is CompletedCourseViewModel completedCourseViewModel) diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_TagRow.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_TagRow.cshtml index d6445cf20c..39be85ce76 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_TagRow.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Shared/_TagRow.cshtml @@ -1,52 +1,63 @@ @using DigitalLearningSolutions.Web.ViewModels.LearningPortal @using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Current +@using DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments @model BaseLearningItemViewModel
- @if (Model.HasDiagnosticAssessment) - { -
- Diagnostic assessment + @if (Model is CurrentCourseViewModel currentCourseViewModel) { +
+ Course
+ @if (currentCourseViewModel.UserIsSupervisor) { +
+ Supervisor +
+ } + @if (currentCourseViewModel.IsEnrolledWithGroup) { +
+ In group +
+ } } - @if (Model.HasLearningContent) - { -
- Learning content + @if (Model is SelfAssessmentCardViewModel) { +
+ Self assessment
} - @if (Model.HasLearningAssessmentAndCertification) - { -
- Learning assessment and certification + @if (Model is LearningResourceCardViewModel resource) { +
+ Learning resource +
+
+ @resource.ResourceType +
+
+ @resource.CatalogueName
} - @if (Model.IsSelfAssessment) - { -
- Competency assessment + @if (Model.HasDiagnosticAssessment) { +
+ Diagnostic assessment
} - @if (Model.IncludesSignposting) - { -
- Signposts learning + @if (Model.HasLearningContent) { +
+ Learning content
} - @if (Model is CurrentCourseViewModel currentCourseViewModel) - { - @if (currentCourseViewModel.UserIsSupervisor) - { -
- Supervisor -
- } - @if (currentCourseViewModel.IsEnrolledWithGroup) - { -
- In group -
- } + @if (Model.HasLearningAssessmentAndCertification) { +
+ Learning assessment and certification +
+ } + @if (Model.IsSelfAssessment) { +
+ Competency assessment +
+ } + @if (Model.IncludesSignposting) { +
+ Signposts learning +
} -