From f10524ce017be572d12ae8b4563e162d99405f7a Mon Sep 17 00:00:00 2001 From: ArunimaGeorge <163844873+ArunimaGeorge@users.noreply.github.com> Date: Mon, 5 Aug 2024 09:17:57 +0100 Subject: [PATCH 01/29] Merge pull request #512 from TechnologyEnhancedLearning/Develope/fixes/TD-4430-Issue-when-adding-duplicate-keywords-on-Keywords-section-when-contributing-resources-and-on-Admin-section TD-4430 : Issue when adding duplicate keywords on 'Keywords' section when contributing resources and on Admin section --- .../Views/Catalogue/Edit.cshtml | 16 ++++++++++++++ .../components/KeyWordsEditor.vue | 16 +++++++------- .../vuesrc/contribute/ContentCommon.vue | 21 +++++++++++-------- .../ResourceService.cs | 3 ++- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml index e0e81edcf..629e03c7c 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml @@ -162,6 +162,11 @@ +
+
+ +
+
@@ -341,6 +346,9 @@ } else { $('#add-keyword').removeAttr('disabled'); } + + $('#keyword-error-span').hide(); + $('#keyword-error-span').html(''); }); $('#add-keyword').on('click', function () { @@ -355,6 +363,8 @@ return item.trim(); }); + var duplicateKeywords = []; + $('#keyword-error-span').hide(); values.forEach(function (value) { if (value && keywords.indexOf(value) === -1) { keywords.push(value); @@ -368,6 +378,12 @@ $(x).attr('name', "Keywords[" + i + "]"); }); } + else + { + duplicateKeywords.push(value); + $('#keyword-error-span').show(); + $('#keyword-error-span').html('The keyword(s) have already been added : ' + duplicateKeywords.join(', ')) + } }); $keywordInput.val(""); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue index bfc00c5aa..38c9c50c7 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue @@ -4,7 +4,7 @@
- This keyword has already been added. + The keyword(s) have already been added : {{formattedkeywordErrorMessage}}
@@ -60,6 +60,7 @@ newKeyword: '', keywordError: false, keywordLengthExceeded: false, + keywordErrorMessage: [] } }, computed: { @@ -69,17 +70,20 @@ newKeywordTrimmed(): string { return this.newKeyword?.trim().replace(/ +(?= )/g, '').toLowerCase(); }, + formattedkeywordErrorMessage(): string { + return this.keywordErrorMessage.join(', '); + }, }, methods: { keywordChange() { this.keywordError = false; this.keywordLengthExceeded = false; + this.keywordErrorMessage = []; }, async addKeyword() { if (this.newKeyword && this.newKeywordTrimmed.length > 0) { let allTrimmedKeyword = this.newKeywordTrimmed.toLowerCase().split(','); allTrimmedKeyword = allTrimmedKeyword.filter(e => String(e).trim()); - if (!this.resourceDetails.resourceKeywords.find(_keyword => allTrimmedKeyword.includes(_keyword.keyword.toLowerCase()))) { for (var i = 0; i < allTrimmedKeyword.length; i++) { let item = allTrimmedKeyword[i]; if (item.length > 0 && item.length <= 50) { @@ -90,8 +94,10 @@ newKeywordObj = await resourceData.addKeyword(this.resourceVersionId, newKeywordObj); if (newKeywordObj.id > 0) { this.resourceDetails.resourceKeywords.push(newKeywordObj); - this.keywordError = false; this.newKeyword = ''; + } else if (newKeywordObj.id == 0) { + this.keywordError = true; + this.keywordErrorMessage.push(item); } else { this.keywordError = true; @@ -103,10 +109,6 @@ this.keywordLengthExceeded = true; } } - } - else { - this.keywordError = true; - } } }, async deleteKeyword(keywordId: number) { diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue index 3741814d3..2f1f07809 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue @@ -84,7 +84,7 @@
- This keyword has already been added. + The keyword(s) have already been added : {{formattedkeywordErrorMessage}}
@@ -292,6 +292,7 @@ ResourceType, resourceProviderId: null, keywordLengthExceeded: false, + keywordErrorMessage:[] }; }, computed: { @@ -340,6 +341,9 @@ return this.$store.state.userProviders.length > 0; } }, + formattedkeywordErrorMessage(): string { + return this.keywordErrorMessage.join(', '); + }, }, created() { this.setInitialValues(); @@ -485,6 +489,7 @@ keywordChange() { this.keywordError = false; this.keywordLengthExceeded = false; + this.keywordErrorMessage = []; }, resetSelectedLicence() { this.resourceLicenceId = 0; @@ -534,7 +539,6 @@ if (this.newKeyword && this.newKeywordTrimmed.length > 0) { let allTrimmedKeyword = this.newKeywordTrimmed.toLowerCase().split(','); allTrimmedKeyword = allTrimmedKeyword.filter(e => String(e).trim()); - if (!this.keywords.find(_keyword => allTrimmedKeyword.includes(_keyword.keyword.toLowerCase()))) { for (var i = 0; i < allTrimmedKeyword.length; i++) { let item = allTrimmedKeyword[i]; if (item.length > 0 && item.length <= 50) { @@ -548,22 +552,21 @@ if (this.resourceDetail.resourceVersionId == 0) { this.$store.commit('setResourceVersionId', newkeywordObj.resourceVersionId) } - this.keywordError = false; this.newKeyword = ''; - } else { + } else if (newkeywordObj.id == 0) { this.keywordError = true; - break; + this.keywordErrorMessage.push(item); } + else { + this.keywordError = true; + break; + } } else { this.keywordLengthExceeded = true; break; } } - } - else { - this.keywordError = true; - } } else { this.newKeyword = ''; diff --git a/WebAPI/LearningHub.Nhs.Services/ResourceService.cs b/WebAPI/LearningHub.Nhs.Services/ResourceService.cs index 808bd8448..76d8df791 100644 --- a/WebAPI/LearningHub.Nhs.Services/ResourceService.cs +++ b/WebAPI/LearningHub.Nhs.Services/ResourceService.cs @@ -1772,7 +1772,8 @@ public async Task AddResourceVersionKeywordAsync(Re bool doesKeywordAlreadyExist = await this.resourceVersionKeywordRepository.DoesResourceVersionKeywordAlreadyExistAsync(rvk.ResourceVersionId, rvk.Keyword); if (doesKeywordAlreadyExist) { - return new LearningHubValidationResult(false, "This keyword has already been added."); + retVal.CreatedId = 0; + return retVal; } retVal.CreatedId = await this.resourceVersionKeywordRepository.CreateAsync(userId, rvk); From 10d728d34dcaa4ef51d4a8b792d1bdbbada054ca Mon Sep 17 00:00:00 2001 From: ArunimaGeorge <163844873+ArunimaGeorge@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:49:03 +0100 Subject: [PATCH 02/29] Merge pull request #499 from TechnologyEnhancedLearning/Develop/fixes/TD-4411-My-learning-page---Completed-filter-along-with-Assessment-doesnt-display-the-correct-results TD-4411: Fixed filter result for 'Completed' filter along with 'Assessment'. --- .../Activity/GetUserLearningActivities.sql | 35 ++++++++++++++++--- .../GetUserLearningActivitiesCount.sql | 34 +++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivities.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivities.sql index c96931937..15272dc4e 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivities.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivities.sql @@ -10,6 +10,7 @@ -- Sarathlal 08-03-2024 -- Sarathlal 23-04-2024 TD-2954: Audio/Video/Assessment issue resolved and duplicate issue also resolved -- Sarathlal 25-04-2024 TD-4067: Resource with muliple version issue resolved +-- Arunima 26-07-2024 TD-4411: "Completed" filter along with "Assessment" doesn't display the correct results ------------------------------------------------------------------------------- CREATE PROCEDURE [activity].[GetUserLearningActivities] ( @userId INT @@ -271,9 +272,35 @@ FROM ( ) ) OR - ([Res].[ResourceTypeId] IN (6,11) AND [ResourceActivity].[ActivityStatusId] = 3) - OR ([Res].[ResourceTypeId] IN (11) AND [ResourceActivity].[ActivityStatusId] = 3 AND [AssessResVer].[AssessmentType]=1) - --OR + ([Res].[ResourceTypeId] IN (6) AND [ResourceActivity].[ActivityStatusId] = 3) + OR ( + EXISTS (SELECT 1 FROM @tmpActivityStatus WHERE ActivityStatusId = 3) + AND + ( + [Res].[ResourceTypeId] = 11 AND [AssessResVer].[AssessmentType]=1 + AND + EXISTS + ( + SELECT 1 + FROM [activity].[AssessmentResourceActivity] AS [AssessmentResourceActivity6] + WHERE + [AssessmentResourceActivity6].[Deleted] = 0 + AND + [ResourceActivity].[Id] = [AssessmentResourceActivity6].[ResourceActivityId] + ) + AND + ( + (SELECT TOP(1) + [AssessmentResourceActivity7].[Score] + FROM [activity].[AssessmentResourceActivity] AS [AssessmentResourceActivity7] + WHERE + [AssessmentResourceActivity7].[Deleted] = 0 + AND [ResourceActivity].[Id] = [AssessmentResourceActivity7].[ResourceActivityId]) >= 0.0 + ) + ) + + ) + --OR --( -- ([Res].[ResourceTypeId] IN (1,5,10,12) AND [ResourceActivity].[ActivityStatusId] = 3) -- AND @@ -507,5 +534,3 @@ LEFT JOIN ( ORDER BY [t2].[ActivityStart] DESC, [t2].[Id], [t2].[Id0], [t2].[Id1], [t2].[Id2], [VideoResourceVersion].[Id], [AudeoResourceVersion].[Id], [t3].[Id], [t4].[Id], [t5].[Id], [t6].[Id], [t7].[Id], [t8].[Id], [t9].[Id], [t10].[Id], [t11].[Id] END - - diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivitiesCount.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivitiesCount.sql index daa20e0e7..7ce53f78c 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivitiesCount.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetUserLearningActivitiesCount.sql @@ -9,6 +9,7 @@ -- Sarathlal 18-12-2023 -- Sarathlal 08-03-2024 -- Sarathlal 23-04-2024 TD-2954: Audio/Video/Assessment issue resolved and duplicate issue also resolved +-- Arunima 26-07-2024 TD-4411: "Completed" filter along with "Assessment" doesn't display the correct results ------------------------------------------------------------------------------- CREATE PROCEDURE [activity].[GetUserLearningActivitiesCount] ( @userId INT @@ -186,10 +187,35 @@ FROM ( ) ) OR - ([Res].[ResourceTypeId] IN (6,11) AND [ResourceActivity].[ActivityStatusId] = 3) - OR ([Res].[ResourceTypeId] IN (11) AND [ResourceActivity].[ActivityStatusId] = 3 AND [AssessResVer].[AssessmentType]=1) - - --OR + ([Res].[ResourceTypeId] IN (6) AND [ResourceActivity].[ActivityStatusId] = 3) + OR ( + EXISTS (SELECT 1 FROM @tmpActivityStatus WHERE ActivityStatusId = 3) + AND + ( + [Res].[ResourceTypeId] = 11 AND [AssessResVer].[AssessmentType]=1 + AND + EXISTS + ( + SELECT 1 + FROM [activity].[AssessmentResourceActivity] AS [AssessmentResourceActivity6] + WHERE + [AssessmentResourceActivity6].[Deleted] = 0 + AND + [ResourceActivity].[Id] = [AssessmentResourceActivity6].[ResourceActivityId] + ) + AND + ( + (SELECT TOP(1) + [AssessmentResourceActivity7].[Score] + FROM [activity].[AssessmentResourceActivity] AS [AssessmentResourceActivity7] + WHERE + [AssessmentResourceActivity7].[Deleted] = 0 + AND [ResourceActivity].[Id] = [AssessmentResourceActivity7].[ResourceActivityId]) >= 0.0 + ) + ) + + ) + --OR --( -- ([Res].[ResourceTypeId] IN (1,5,10,12) AND [ResourceActivity].[ActivityStatusId] = 3) -- AND From af5bf57a0c22533a3bfbc4ce385f6bbfd7f23cce Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:22:31 +0100 Subject: [PATCH 03/29] fixes --- .../ContributeAssessmentSettings.vue | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue index 5a20df8ad..ab3a50bd0 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue @@ -65,15 +65,14 @@
-
Provide guidance for the learner at the end of this assessment.
- - Provide guidance for the learner at the end of this assessment.
+ +
From 661c6096344113b58aadbc0c30449248330240f0 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:03:29 +0100 Subject: [PATCH 04/29] Merge pull request #517 from TechnologyEnhancedLearning/Openapi-Changes Merge Openapi Changes-NHS staff app --- .gitignore | 2 + .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- .../ViewModels/ResourceMetadataViewModel.cs | 20 ++- ...ceReferenceWithResourceDetailsViewModel.cs | 24 ++- .../Repositories/IResourceRepository.cs | 9 ++ .../EntityFramework/LearningHubDbContext.cs | 17 +- .../EntityFramework/ServiceMappings.cs | 4 +- .../Map/Content/PageSectionDetailMap.cs | 4 +- .../Map/LogMap.cs | 4 - .../Blocks/WholeSlideImageAnnotationMap.cs | 10 +- .../WholeSlideImageAnnotationMarkMap.cs | 14 +- .../Map/Resources/ResourceVersionMap.cs | 3 + .../Repositories/ResourceRepository.cs | 33 ++++ ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../Services/IResourceService.cs | 4 +- .../Services/ISearchService.cs | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 3 +- .../Services/ResourceService.cs | 49 +++++- .../Services/SearchService.cs | 35 +++- .../Controllers/ResourceControllerTests.cs | 58 +++++-- .../Services/Services/ResourceServiceTests.cs | 97 ++++++++--- .../Services/Services/SearchServiceTests.cs | 16 +- .../Controllers/OpenApiControllerBase.cs | 41 +++++ .../Controllers/ResourceController.cs | 10 +- .../LearningHub.NHS.OpenAPI.csproj.user | 9 ++ .../LearningHub.Nhs.Database.sqlproj | 8 +- ...esourceActivityPerResourceMajorVersion.sql | 152 ++++++++++++++++++ 27 files changed, 536 insertions(+), 96 deletions(-) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetResourceActivityPerResourceMajorVersion.sql diff --git a/.gitignore b/.gitignore index 4119589ad..04baa838f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ obj /WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Database/LearningHub.Nhs.Migration.Staging.Database.dbmdl /WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Database/LearningHub.Nhs.Migration.Staging.Database.jfm /LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.Development.json +/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.Development.json +/OpenAPI/LearningHub.Nhs.OpenApi/web.config diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 0047f4483..2c87ba2cb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceMetadataViewModel.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceMetadataViewModel.cs index 3675959a6..a4c1fbf4c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceMetadataViewModel.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceMetadataViewModel.cs @@ -1,7 +1,9 @@ namespace LearningHub.Nhs.OpenApi.Models.ViewModels { + using LearningHub.Nhs.Models.Entities.Activity; using System.Collections.Generic; + /// /// Class. /// @@ -23,20 +25,25 @@ public ResourceMetadataViewModel() /// . /// . /// . + /// . public ResourceMetadataViewModel( int resourceId, string title, string description, List references, string resourceType, - decimal rating) + int? majorVersion, + decimal rating, + List userSummaryActivityStatuses) { this.ResourceId = resourceId; this.Title = title; this.Description = description; this.References = references; this.ResourceType = resourceType; + this.MajorVersion = majorVersion; this.Rating = rating; + this.UserSummaryActivityStatuses = userSummaryActivityStatuses; } /// @@ -64,9 +71,20 @@ public ResourceMetadataViewModel( /// public string ResourceType { get; set; } + /// + /// Gets or sets . + /// + public int? MajorVersion { get; set; } + + /// /// Gets or sets . /// public decimal Rating { get; set; } + + /// + /// Gets or sets . + /// + public List UserSummaryActivityStatuses { get; set; } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceReferenceWithResourceDetailsViewModel.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceReferenceWithResourceDetailsViewModel.cs index cf31bdf54..41d1b197b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceReferenceWithResourceDetailsViewModel.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/ResourceReferenceWithResourceDetailsViewModel.cs @@ -1,3 +1,6 @@ +using LearningHub.Nhs.Models.Entities.Activity; +using System.Collections.Generic; + namespace LearningHub.Nhs.OpenApi.Models.ViewModels { /// @@ -14,8 +17,10 @@ public class ResourceReferenceWithResourceDetailsViewModel /// . /// . /// . + /// /// . /// . + /// public ResourceReferenceWithResourceDetailsViewModel( int resourceId, int refId, @@ -23,17 +28,21 @@ public ResourceReferenceWithResourceDetailsViewModel( string description, CatalogueViewModel catalogueViewModel, string resourceType, + int? majorVersion, decimal rating, - string link) + string link, + List userSummaryActivityStatuses) { this.ResourceId = resourceId; this.RefId = refId; this.Title = title; this.Description = description; this.Catalogue = catalogueViewModel; + this.MajorVersion = majorVersion; this.ResourceType = resourceType; this.Rating = rating; this.Link = link; + this.UserSummaryActivityStatuses = userSummaryActivityStatuses; } /// @@ -66,14 +75,27 @@ public ResourceReferenceWithResourceDetailsViewModel( /// public string ResourceType { get; } + + /// + /// Gets . + /// + public int? MajorVersion { get; } + /// /// Gets . /// + /// + public decimal Rating { get; } /// /// Gets . /// public string Link { get; } + + /// + /// Gets . + /// + public List UserSummaryActivityStatuses { get; } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs index 50eff71f6..d3a1856e4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs @@ -2,6 +2,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories { using System.Collections.Generic; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; /// @@ -23,5 +24,13 @@ public interface IResourceRepository /// Resource references. public Task> GetResourceReferencesByOriginalResourceReferenceIds( IEnumerable originalResourceReferenceIds); + + /// + /// Gets resource activity for resourceReferenceIds and userIds. + /// + /// . + /// + /// ResourceActivityDTO. + Task> GetResourceActivityPerResourceMajorVersion(IEnumerable? resourceReferenceIds, IEnumerable? userIds); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index 020a4f1f5..c1ebf721b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -295,6 +295,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet FileChunkDetail { get; set; } + /// + /// Gets or sets the ResourceActivityDto. These are not entities. They are returned from the [activity].[GetResourceActivityPerResourceMajorVersion] stored proc.. + /// + public virtual DbSet ResourceActivityDTO { get; set; } + /// /// Gets or sets the RecentlyAddedResources. These are not entities. They are returned from the [resources].[GetRecentlyAddedResources] stored proc.. /// @@ -312,14 +317,14 @@ public LearningHubDbContext(LearningHubDbContextOptions options) public virtual DbSet DashboardResourceDto { get; set; } /// - /// Gets or sets the ScormContentDetailsViewModel. + /// Gets or sets the ExternalContentDetailsViewModel. /// - public virtual DbSet ScormContentDetailsViewModel { get; set; } + public virtual DbSet ExternalContentDetailsViewModel { get; set; } /// - /// Gets or sets the ScormContentServerViewModel. + /// Gets or sets the ContentServerViewModel. /// - public virtual DbSet ScormContentServerViewModel { get; set; } + public virtual DbSet ContentServerViewModel { get; set; } /// /// Gets or sets the DashboardCatalogueDto @@ -520,12 +525,12 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// /// Gets or sets the whole slide image annotation. /// - public virtual DbSet WholeSlideImageAnnotation { get; set; } + public virtual DbSet ImageAnnotation { get; set; } /// /// Gets or sets the whole slide image annotation mark. /// - public virtual DbSet WholeSlideImageAnnotationMark { get; set; } + public virtual DbSet ImageAnnotationMark { get; set; } /// /// Gets or sets the media block. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 668d318b1..8db1cd876 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -110,8 +110,8 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Content/PageSectionDetailMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Content/PageSectionDetailMap.cs index 1bdbb69c8..42d2e309f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Content/PageSectionDetailMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Content/PageSectionDetailMap.cs @@ -17,8 +17,6 @@ protected override void InternalMap(EntityTypeBuilder entity) { entity.ToTable("PageSectionDetail", "content"); - entity.Property(e => e.AssetPositionId).HasDefaultValueSql("((2))"); - entity.Property(e => e.BackgroundColour).HasMaxLength(20); entity.Property(e => e.Description).HasMaxLength(512); @@ -31,7 +29,7 @@ protected override void InternalMap(EntityTypeBuilder entity) entity.Property(e => e.TextColour).HasMaxLength(20); - entity.Property(e => e.Title).HasMaxLength(128); + entity.Property(e => e.SectionTitle).HasMaxLength(128); entity.Property(e => e.DeletePending).IsRequired(false); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/LogMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/LogMap.cs index 62ebffd0c..0b8bb7428 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/LogMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/LogMap.cs @@ -68,10 +68,6 @@ protected void InternalMap(EntityTypeBuilder modelBuilder) modelBuilder.Property(e => e.UserId) .HasColumnName("UserId"); - modelBuilder.HasOne(d => d.User) - .WithMany(p => p.Logs) - .HasForeignKey(d => d.UserId) - .OnDelete(DeleteBehavior.ClientSetNull); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs index 18a94369a..00f7f46cb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs @@ -7,7 +7,7 @@ /// /// The whole slide image annotation map. /// - public class WholeSlideImageAnnotationMap : BaseEntityMap + public class ImageAnnotationMap : BaseEntityMap { /// /// The internal map. @@ -15,15 +15,15 @@ public class WholeSlideImageAnnotationMap : BaseEntityMap /// The model builder. /// - protected override void InternalMap(EntityTypeBuilder modelBuilder) + protected override void InternalMap(EntityTypeBuilder modelBuilder) { - modelBuilder.ToTable("WholeSlideImageAnnotation", "resources"); + modelBuilder.ToTable("ImageAnnotation", "resources"); modelBuilder.HasOne(a => a.WholeSlideImage) - .WithMany(i => i.WholeSlideImageAnnotations) + .WithMany(i => i.ImageAnnotations) .HasForeignKey(a => a.WholeSlideImageId) .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_WholeSlideImageAnnotation_WholeSlideImageId"); + .HasConstraintName("FK_ImageAnnotation_WholeSlideImageId"); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs index 2720b23db..9db0a6db6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs @@ -7,7 +7,7 @@ /// /// The whole slide image annotation map. /// - public class WholeSlideImageAnnotationMarkMap : BaseEntityMap + public class ImageAnnotationMarkMap : BaseEntityMap { /// /// The internal map. @@ -15,15 +15,15 @@ public class WholeSlideImageAnnotationMarkMap : BaseEntityMap /// The model builder. /// - protected override void InternalMap(EntityTypeBuilder modelBuilder) + protected override void InternalMap(EntityTypeBuilder modelBuilder) { - modelBuilder.ToTable("WholeSlideImageAnnotationMark", "resources"); + modelBuilder.ToTable("ImageAnnotationMark", "resources"); - modelBuilder.HasOne(a => a.WholeSlideImageAnnotation) - .WithMany(i => i.WholeSlideImageAnnotationMarks) - .HasForeignKey(a => a.WholeSlideImageAnnotationId) + modelBuilder.HasOne(a => a.ImageAnnotation) + .WithMany(i => i.ImageAnnotationMarks) + .HasForeignKey(a => a.ImageAnnotationId) .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_WholeSlideImageAnnotationMark_WholeSlideImageAnnotationId"); + .HasConstraintName("FK_ImageAnnotationMark_ImageAnnotationId"); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionMap.cs index a87bee808..d96c3b4fe 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionMap.cs @@ -39,6 +39,9 @@ protected override void InternalMap(EntityTypeBuilder modelBuil .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK_ResourceVersion_Resource"); + modelBuilder.Property(e => e.ResourceAccessibilityEnum).HasColumnName("ResourceAccessibilityId") + .HasConversion(); + modelBuilder.Property(e => e.VersionStatusEnum).HasColumnName("VersionStatusId") .HasConversion(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs index a826c627e..7ffb3c6fa 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs @@ -1,11 +1,14 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories { + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; /// @@ -69,5 +72,35 @@ public async Task> GetResourceReferencesByOrigina .ThenInclude(r => r.ResourceVersionRatingSummary) .ToListAsync(); } + + /// + /// + /// + /// . + /// A representing the result of the asynchronous operation. + public async Task> GetResourceActivityPerResourceMajorVersion( + IEnumerable? resourceReferenceIds, IEnumerable? userIds) + { + var resourceIdsParam = resourceReferenceIds != null + ? string.Join(",", resourceReferenceIds) + : null; + + var userIdsParam = userIds != null + ? string.Join(",", userIds) + : null; + + var resourceIdsParameter = new SqlParameter("@p0", resourceIdsParam ?? (object)DBNull.Value); + var userIdsParameter = new SqlParameter("@p1", userIdsParam ?? (object)DBNull.Value); + + List resourceActivityDTOs = await dbContext.ResourceActivityDTO + .FromSqlRaw( + "[activity].[GetResourceActivityPerResourceMajorVersion] @p0, @p1", + resourceIdsParameter, + userIdsParameter) + .AsNoTracking() + .ToListAsync(); + + return resourceActivityDTOs; + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 6372c89b6..108c74355 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index 8b2e46dee..795ddbf42 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -15,13 +15,13 @@ public interface IResourceService /// /// The original resource reference id. /// The the resourceMetaDataViewModel corresponding to the resource reference. - Task GetResourceReferenceByOriginalId(int originalResourceReferenceId); + Task GetResourceReferenceByOriginalId(int originalResourceReferenceId, int? currentUserId); /// /// The get resources by Ids endpoint. /// /// The original resource reference Ids. /// The resourceReferenceMetaDataViewModel. - Task GetResourceReferencesByOriginalIds(List originalResourceReferenceIds); + Task GetResourceReferencesByOriginalIds(List originalResourceReferenceIds, int? currentUserId); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs index 63155c5eb..c9b6e3031 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs @@ -14,6 +14,6 @@ public interface ISearchService /// /// . /// . - Task Search(ResourceSearchRequest query); + Task Search(ResourceSearchRequest query, int? currentUserId); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index 2207dfdd4..ce9034d03 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -24,6 +24,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index 1e3f4e563..3212b0b37 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -2,10 +2,13 @@ namespace LearningHub.Nhs.OpenApi.Services.Services { using System; using System.Collections.Generic; + using System.Data; using System.Linq; using System.Net; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.ViewModels.Helpers; using LearningHub.Nhs.OpenApi.Models.Exceptions; using LearningHub.Nhs.OpenApi.Models.ViewModels; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; @@ -47,9 +50,11 @@ public ResourceService(ILearningHubService learningHubService, IResourceReposito /// the get by id async. /// /// the id. + /// /// the resource. - public async Task GetResourceReferenceByOriginalId(int originalResourceReferenceId) + public async Task GetResourceReferenceByOriginalId(int originalResourceReferenceId, int? currentUserId) { + List resourceActivities = new List() { }; var list = new List() { originalResourceReferenceId }; var resourceReferences = await this.resourceRepository.GetResourceReferencesByOriginalResourceReferenceIds(list); @@ -64,7 +69,15 @@ public async Task GetResourceRefe throw new HttpResponseException("No matching resource reference", HttpStatusCode.NotFound); } - return this.GetResourceReferenceWithResourceDetailsViewModel(resourceReference); + if (currentUserId.HasValue) + { + List resourceIds = new List() { resourceReference.ResourceId }; + List userIds = new List() { currentUserId.Value }; + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; + } + + return this.GetResourceReferenceWithResourceDetailsViewModel(resourceReference, resourceActivities); } catch (InvalidOperationException exception) { @@ -78,8 +91,11 @@ public async Task GetResourceRefe /// /// the resource reference ids. /// the resource. - public async Task GetResourceReferencesByOriginalIds(List originalResourceReferenceIds) + public async Task GetResourceReferencesByOriginalIds(List originalResourceReferenceIds, int? currentUserId) { + List resourceActivities = new List() { }; + List majorVersionIdActivityStatusDescription = new List() { }; + var resourceReferences = await this.resourceRepository.GetResourceReferencesByOriginalResourceReferenceIds(originalResourceReferenceIds); var resourceReferencesList = resourceReferences.ToList(); var matchedIds = resourceReferencesList.Select(r => r.OriginalResourceReferenceId).ToList(); @@ -95,18 +111,33 @@ public async Task GetResourceReferencesByOrigina this.logger.LogWarning($"Multiple resource references found with OriginalResourceReferenceId {duplicateIds.First()}"); } - var matchedResources = resourceReferencesList - .Select(this.GetResourceReferenceWithResourceDetailsViewModel) - .ToList(); + if (currentUserId.HasValue) + { + List resourceIds = resourceReferencesList.Select(rrl => rrl.ResourceId).ToList(); + List userIds = new List() { currentUserId.Value }; + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; + } + + List matchedResources = resourceReferencesList + .Select(rr => this.GetResourceReferenceWithResourceDetailsViewModel(rr, resourceActivities.Where(ra => ra.ResourceId == rr.ResourceId).ToList())) + .ToList(); return new BulkResourceReferenceViewModel(matchedResources, unmatchedIds); } - private ResourceReferenceWithResourceDetailsViewModel GetResourceReferenceWithResourceDetailsViewModel(ResourceReference resourceReference) + private ResourceReferenceWithResourceDetailsViewModel GetResourceReferenceWithResourceDetailsViewModel(ResourceReference resourceReference, List resourceActivities) { var hasCurrentResourceVersion = resourceReference.Resource.CurrentResourceVersion != null; var hasRating = resourceReference.Resource.CurrentResourceVersion?.ResourceVersionRatingSummary != null; + List majorVersionIdActivityStatusDescription = new List() { }; + + if (resourceActivities != null && resourceActivities.Count != 0) + { + majorVersionIdActivityStatusDescription = ActivityStatusHelper.GetMajorVersionIdActivityStatusDescriptionLSPerResource(resourceReference.Resource, resourceActivities).ToList(); + } + if (resourceReference.Resource == null) { throw new Exception("No matching resource"); @@ -135,8 +166,10 @@ private ResourceReferenceWithResourceDetailsViewModel GetResourceReferenceWithRe resourceReference.Resource.CurrentResourceVersion?.Description ?? string.Empty, resourceReference.GetCatalogue(), resourceTypeNameOrEmpty, + resourceReference.Resource?.CurrentResourceVersion?.MajorVersion ?? 0, resourceReference.Resource?.CurrentResourceVersion?.ResourceVersionRatingSummary?.AverageRating ?? 0, - this.learningHubService.GetResourceLaunchUrl(resourceReference.OriginalResourceReferenceId)); + this.learningHubService.GetResourceLaunchUrl(resourceReference.OriginalResourceReferenceId), + majorVersionIdActivityStatusDescription); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index 656a8cb14..c5ede76fb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -3,8 +3,11 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.ViewModels.Helpers; using LearningHub.Nhs.OpenApi.Models.ServiceModels.Findwise; using LearningHub.Nhs.OpenApi.Models.ServiceModels.Resource; using LearningHub.Nhs.OpenApi.Models.ViewModels; @@ -57,7 +60,7 @@ public SearchService( } /// - public async Task Search(ResourceSearchRequest query) + public async Task Search(ResourceSearchRequest query, int? currentUserId) { var findwiseResultModel = await this.findwiseClient.Search(query); @@ -66,7 +69,7 @@ public async Task Search(ResourceSearchRequest query) return ResourceSearchResultModel.FailedWithStatus(findwiseResultModel.FindwiseRequestStatus); } - var resourceMetadataViewModels = await this.GetResourceMetadataViewModels(findwiseResultModel); + var resourceMetadataViewModels = await this.GetResourceMetadataViewModels(findwiseResultModel, currentUserId); var totalHits = findwiseResultModel.SearchResults?.Stats.TotalHits; @@ -77,8 +80,9 @@ public async Task Search(ResourceSearchRequest query) } private async Task> GetResourceMetadataViewModels( - FindwiseResultModel findwiseResultModel) + FindwiseResultModel findwiseResultModel, int? currentUserId) { + List resourceActivities = new List() { }; var documentsFound = findwiseResultModel.SearchResults?.DocumentList.Documents?.ToList() ?? new List(); var findwiseResourceIds = documentsFound.Select(d => int.Parse(d.Id)).ToList(); @@ -90,7 +94,7 @@ private async Task> GetResourceMetadataViewModel var resourcesFound = await this.resourceRepository.GetResourcesFromIds(findwiseResourceIds); - var resourceMetadataViewModels = resourcesFound.Select(this.MapToViewModel) + List resourceMetadataViewModels = resourcesFound.Select(resource => MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) .OrderBySequence(findwiseResourceIds) .ToList(); @@ -105,14 +109,29 @@ private async Task> GetResourceMetadataViewModel unmatchedResourcesIdsString); } + if (currentUserId.HasValue) + { + List resourceIds = resourcesFound.Select(x => x.Id).ToList(); + List userIds = new List() { currentUserId.Value }; + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; + } return resourceMetadataViewModels; } - private ResourceMetadataViewModel MapToViewModel(Resource resource) + private ResourceMetadataViewModel MapToViewModel(Resource resource, List resourceActivities) { var hasCurrentResourceVersion = resource.CurrentResourceVersion != null; var hasRating = resource.CurrentResourceVersion?.ResourceVersionRatingSummary != null; + List majorVersionIdActivityStatusDescription = new List() { }; + + if (resourceActivities != null && resourceActivities.Count != 0) + { + majorVersionIdActivityStatusDescription = ActivityStatusHelper.GetMajorVersionIdActivityStatusDescriptionLSPerResource(resource, resourceActivities) + .ToList(); + } + if (!hasCurrentResourceVersion) { this.logger.LogInformation( @@ -131,13 +150,17 @@ private ResourceMetadataViewModel MapToViewModel(Resource resource) this.logger.LogError($"Resource has unrecognised type: {resource.ResourceTypeEnum}"); } + return new ResourceMetadataViewModel( resource.Id, resource.CurrentResourceVersion?.Title ?? ResourceHelpers.NoResourceVersionText, resource.CurrentResourceVersion?.Description ?? string.Empty, resource.ResourceReference.Select(this.GetResourceReferenceViewModel).ToList(), resourceTypeNameOrEmpty, - resource.CurrentResourceVersion?.ResourceVersionRatingSummary?.AverageRating ?? 0.0m); + resource.CurrentResourceVersion?.MajorVersion ?? 0, + resource.CurrentResourceVersion?.ResourceVersionRatingSummary?.AverageRating ?? 0.0m, + majorVersionIdActivityStatusDescription + ); } private ResourceReferenceViewModel GetResourceReferenceViewModel( diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs index 49f901c72..acde1d3ef 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs @@ -18,8 +18,11 @@ namespace LearningHub.Nhs.OpenApi.Tests.Controllers using Moq; using Newtonsoft.Json; using Xunit; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using System.Security.Claims; - public sealed class ResourceControllerTests : IDisposable + public sealed class ResourceControllerTests { private readonly Mock searchService; private readonly Mock resourceService; @@ -87,6 +90,7 @@ await Assert.ThrowsAsync( public async Task SearchEndpointUsesDefaultLimitGivenInConfig() { // Given + int? currentUserId = null; //E.g if hitting endpoint with ApiKey auth this.GivenSearchServiceSucceedsButFindsNoItems(); this.GivenDefaultLimitForFindwiseSearchIs(12); this.resourceController = new ResourceController( @@ -99,7 +103,7 @@ public async Task SearchEndpointUsesDefaultLimitGivenInConfig() // Then this.searchService.Verify( - service => service.Search(It.Is(request => request.Limit == 12))); + service => service.Search(It.Is(request => request.Limit == 12), currentUserId)); } [Fact] @@ -177,6 +181,41 @@ await Assert.ThrowsAsync( exception.StatusCode.Should().Be(HttpStatusCode.BadRequest); } + [Fact] + public void CurrentUserIdSetByAuth() + { + // Arrange + ResourceController resourceController = new ResourceController( + this.searchService.Object, + this.resourceService.Object, + this.findwiseConfigOptions.Object + ); + + + // This Id is the development accountId + int currentUserId = 57541; + + // Create claims identity with the specified user id + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, currentUserId.ToString()), + }; + var identity = new ClaimsIdentity(claims, "AuthenticationTypes.Federation"); // Set the authentication type to "Federation" + + // Create claims principal with the claims identity + var claimsPrincipal = new ClaimsPrincipal(identity); + + // Create a mock HttpContext and set it to the ControllerContext + var httpContext = new DefaultHttpContext { User = claimsPrincipal }; + var controllerContext = new ControllerContext { HttpContext = httpContext }; + resourceController.ControllerContext = controllerContext; + + // Act + + // Assert that the CurrentUserId property of the resourceController matches the currentUserId + Assert.Equal(currentUserId, resourceController.CurrentUserId); + } + [Theory] [InlineData(1)] [InlineData(20)] @@ -184,6 +223,7 @@ await Assert.ThrowsAsync( public async Task SearchEndpointUsesPassedInLimitIfGiven(int limit) { // Given + int? currentUserId = null; //E.g if hitting endpoint with ApiKey auth this.GivenSearchServiceSucceedsButFindsNoItems(); this.GivenDefaultLimitForFindwiseSearchIs(20); this.resourceController = new ResourceController( @@ -196,12 +236,7 @@ public async Task SearchEndpointUsesPassedInLimitIfGiven(int limit) // Then this.searchService.Verify( - service => service.Search(It.Is(request => request.Limit == limit))); - } - - public void Dispose() - { - this.resourceController?.Dispose(); + service => service.Search(It.Is(request => request.Limit == limit), currentUserId)); } private void GivenDefaultLimitForFindwiseSearchIs(int limit) @@ -212,14 +247,17 @@ private void GivenDefaultLimitForFindwiseSearchIs(int limit) private void GivenSearchServiceFailsWithStatus(FindwiseRequestStatus status) { - this.searchService.Setup(ss => ss.Search(It.IsAny())).ReturnsAsync( + int? currentUserId = null; //E.g if hitting endpoint with ApiKey auth + this.searchService.Setup(ss => ss.Search(It.IsAny(), currentUserId)).ReturnsAsync( new ResourceSearchResultModel(new List(), status, 0)); } private void GivenSearchServiceSucceedsButFindsNoItems() { - this.searchService.Setup(ss => ss.Search(It.IsAny())).ReturnsAsync( + int? currentUserId = null; //E.g if hitting endpoint with ApiKey auth + this.searchService.Setup(ss => ss.Search(It.IsAny(), currentUserId)).ReturnsAsync( new ResourceSearchResultModel(new List(), FindwiseRequestStatus.Success, 0)); } + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs index d6ae8f3db..1f791a353 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs @@ -2,10 +2,12 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services { using System; using System.Collections.Generic; + using System.Linq; using System.Net; using System.Threading.Tasks; using FizzWare.NBuilder; using FluentAssertions; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.OpenApi.Models.Exceptions; @@ -22,14 +24,25 @@ public class ResourceServiceTests private readonly Mock learningHubService; private readonly ResourceService resourceService; private readonly Mock resourceRepository; + private readonly int currentUserId; public ResourceServiceTests() { + //This Id is the development accountId + this.currentUserId = 57541; + this.learningHubService = new Mock(); this.resourceRepository = new Mock(); this.resourceService = new ResourceService(this.learningHubService.Object, this.resourceRepository.Object, new NullLogger()); } - + private List ResourceActivityDTOList => new List() + { + new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 5, MajorVersion = 5 }, + new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 7, MajorVersion = 4 }, + new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 3, MajorVersion = 3 }, + new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 7, MajorVersion = 2 }, + new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 3, MajorVersion = 1 }, + }; private List ResourceList => new List() { ResourceTestHelper.CreateResourceWithDetails(id: 1, title: "title1", description: "description1", rating: 3m, resourceType: ResourceTypeEnum.Article), @@ -63,7 +76,7 @@ public async Task SingleResourceEndpointReturnsTheCorrectInformationIfThereIsAMa .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(1); + var x = await this.resourceService.GetResourceReferenceByOriginalId(1, null); // Then x.Rating.Should().Be(3); @@ -80,7 +93,7 @@ public async Task SingleResourceReturnsA404IfTheresNoResourceReferenceWithAMatch .ReturnsAsync(new List()); // When / Then - var exception = await Assert.ThrowsAsync(async () => await this.resourceService.GetResourceReferenceByOriginalId(999)); + var exception = await Assert.ThrowsAsync(async () => await this.resourceService.GetResourceReferenceByOriginalId(999, null)); exception.StatusCode.Should().Be(HttpStatusCode.NotFound); exception.ResponseBody.Should().Be("No matching resource reference"); } @@ -93,7 +106,7 @@ public async Task SingleResourceEndpointReturnsAResourceMetadataViewModelObjectW .ReturnsAsync(this.ResourceReferenceList.GetRange(1, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(2); + var x = await this.resourceService.GetResourceReferenceByOriginalId(2, null); // Then x.Title.Should().Be("No current resource version"); @@ -108,7 +121,7 @@ public async Task SingleResourceEndpointReturnsAMessageSayingNoCatalogueIfThereI .ReturnsAsync(this.ResourceReferenceList.GetRange(2, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(3); + var x = await this.resourceService.GetResourceReferenceByOriginalId(3, null); // Then x.Catalogue.Name.Should().Be("No catalogue for resource reference"); @@ -122,7 +135,7 @@ public async Task SingleResourceEndpointReturnsAMessageSayingNoCatalogueIfThereI .ReturnsAsync(this.ResourceReferenceList.GetRange(3, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(4); + var x = await this.resourceService.GetResourceReferenceByOriginalId(4, null); // Then x.Catalogue.Name.Should().Be("No catalogue for resource reference"); @@ -136,7 +149,7 @@ public async Task SingleResourceEndpointReturnsAMessageSayingNoCatalogueIfThereI .ReturnsAsync(this.ResourceReferenceList.GetRange(5, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(6); + var x = await this.resourceService.GetResourceReferenceByOriginalId(6, null); // Then x.Catalogue.Name.Should().Be("No catalogue for resource reference"); @@ -150,7 +163,7 @@ public async Task SingleResourceEndpointReturnsAZeroForRatingIfTheresNoRatingSum .ReturnsAsync(this.ResourceReferenceList.GetRange(7, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(8); + var x = await this.resourceService.GetResourceReferenceByOriginalId(8, null); // Then x.Catalogue.Name.Should().Be("catalogue3"); @@ -165,7 +178,7 @@ public async Task SingleResourceEndpointThrowsAnErrorAndReturnsABlankStringIfThe .ReturnsAsync(this.ResourceReferenceList.GetRange(8, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(9); + var x = await this.resourceService.GetResourceReferenceByOriginalId(9, null); // Then x.ResourceType.Should().Be(string.Empty); @@ -179,7 +192,7 @@ public async Task SingleResourceEndpointThrowsAnErrorIfThereIsMoreThanOneResourc .ReturnsAsync(this.ResourceReferenceList.GetRange(9, 2)); // When / Then - await Assert.ThrowsAsync(async () => await this.resourceService.GetResourceReferenceByOriginalId(10)); + await Assert.ThrowsAsync(async () => await this.resourceService.GetResourceReferenceByOriginalId(10, null)); } /*[Fact] @@ -198,7 +211,7 @@ public async Task BulkEndpointReturnsAllMatchingResources() .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 2)); // When - var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp); + var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp, null); // Then x.ResourceReferences.Count.Should().Be(2); @@ -220,7 +233,7 @@ public async Task BulkEndpointReturnsA404IfThereAreNoMatchingResources() .ReturnsAsync(new List()); // When - var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp); + var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp, null); // Then x.UnmatchedResourceReferenceIds.Count.Should().Be(2); @@ -237,7 +250,7 @@ public async Task BulkEndpointReturnsResourcesWithIncompleteInformation() .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 4)); // When - var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp); + var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp, null); // Then x.ResourceReferences.Count.Should().Be(4); @@ -257,7 +270,7 @@ public async Task BulkEndpointReturnsUnmatchedResourcesWithMatchedResources() .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 1)); // When - var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp); + var x = await this.resourceService.GetResourceReferencesByOriginalIds(idsToLookUp, null); // Then x.ResourceReferences.Count.Should().Be(1); @@ -277,7 +290,7 @@ public async Task ResourceServiceReturnsTheOriginalResourceReferenceIdAsTheRefId .ReturnsAsync(this.ResourceReferenceList.GetRange(5, 2)); // When - var x = await this.resourceService.GetResourceReferencesByOriginalIds(list); + var x = await this.resourceService.GetResourceReferencesByOriginalIds(list, null); // Then x.ResourceReferences[0].RefId.Should().Be(6); @@ -293,7 +306,7 @@ public async Task ResourceServiceReturnsTheOriginalResourceReferenceIdAsTheRefId .ReturnsAsync(this.ResourceReferenceList.GetRange(5, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(6); + var x = await this.resourceService.GetResourceReferenceByOriginalId(6, null); // Then x.RefId.Should().Be(6); @@ -308,7 +321,7 @@ public async Task ResourceServiceReturnsThatARestrictedCatalogueIsRestricted() .ReturnsAsync(this.ResourceReferenceList.GetRange(8, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(9); + var x = await this.resourceService.GetResourceReferenceByOriginalId(9, null); // Then x.Catalogue.IsRestricted.Should().BeTrue(); @@ -323,10 +336,58 @@ public async Task ResourceServiceReturnsThatAnUnrestrictedCatalogueIsUnrestricte .ReturnsAsync(this.ResourceReferenceList.GetRange(7, 1)); // When - var x = await this.resourceService.GetResourceReferenceByOriginalId(8); + var x = await this.resourceService.GetResourceReferenceByOriginalId(8, null); // Then x.Catalogue.IsRestricted.Should().BeFalse(); } + + [Fact] + public async Task SingleResourceEndpointReturnsActivitySummaryWhenCurrentUserIdProvided() + { + // Given + this.resourceRepository.Setup(rr => rr.GetResourceReferencesByOriginalResourceReferenceIds(new List() { 1 })) + .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 1)); + + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(new List() { 1 }, new List() { currentUserId })) + .ReturnsAsync(this.ResourceActivityDTOList.ToList()); + + // When + var x = await this.resourceService.GetResourceReferenceByOriginalId(1, currentUserId); + + // Then + x.UserSummaryActivityStatuses.Should().NotBeNull(); + x.UserSummaryActivityStatuses[0].MajorVersionId.Should().Be(5); + x.UserSummaryActivityStatuses[1].MajorVersionId.Should().Be(4); + x.UserSummaryActivityStatuses[2].MajorVersionId.Should().Be(3); + x.UserSummaryActivityStatuses[3].MajorVersionId.Should().Be(2); + x.UserSummaryActivityStatuses[4].MajorVersionId.Should().Be(1); + + x.UserSummaryActivityStatuses[0].ActivityStatusDescription.Should().Be("Passed"); + x.UserSummaryActivityStatuses[1].ActivityStatusDescription.Should().Be("In progress"); + x.UserSummaryActivityStatuses[2].ActivityStatusDescription.Should().Be("Viewed"); + x.UserSummaryActivityStatuses[3].ActivityStatusDescription.Should().Be("In progress"); + x.UserSummaryActivityStatuses[4].ActivityStatusDescription.Should().Be("Viewed"); + + } + + [Fact] + public async Task SingleResourceEndpointReturnsEmptyActivitySummaryWhenNoCurrentUserIdProvided() + { + // Given + this.resourceRepository.Setup(rr => rr.GetResourceReferencesByOriginalResourceReferenceIds(new List() { 1 })) + .ReturnsAsync(this.ResourceReferenceList.GetRange(0, 1)); + + // This should not be hit + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(new List() { 1 }, new List() { currentUserId })) + .ReturnsAsync(this.ResourceActivityDTOList.ToList()); + + // When + var x = await this.resourceService.GetResourceReferenceByOriginalId(1, null); + + // Then + x.UserSummaryActivityStatuses.Should().BeEmpty(); + + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs index d393ce59d..ee84c1c51 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs @@ -6,6 +6,7 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using FizzWare.NBuilder; using FluentAssertions; using FluentAssertions.Execution; + using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Search; @@ -73,7 +74,7 @@ public async Task SearchPassesQueryOnToFindwise() .ReturnsAsync(FindwiseResultModel.Failure(FindwiseRequestStatus.Timeout)); // When - await this.searchService.Search(searchRequest); + await this.searchService.Search(searchRequest, null); // Then this.findwiseClient.Verify(fc => fc.Search(searchRequest)); @@ -90,7 +91,7 @@ public async Task SearchReturnsTotalHitsAndSearchResult() this.GivenFindwiseReturnsSuccessfulResponse(74, Enumerable.Range(1, 34)); // When - var searchResult = await this.searchService.Search(searchRequest); + var searchResult = await this.searchService.Search(searchRequest, null); // Then searchResult.Resources.Count.Should().Be(34); @@ -137,7 +138,7 @@ public async Task SearchResultsReturnExpectedValues() this.GivenFindwiseReturnsSuccessfulResponse(2, new[] { 1, 2, 3 }); // When - var searchResult = await this.searchService.Search(searchRequest); + var searchResult = await this.searchService.Search(searchRequest, null); // Then searchResult.Resources.Count.Should().Be(2); @@ -180,7 +181,7 @@ public async Task SearchReturnsResourcesInOrderMatchingFindwise() .ReturnsAsync(resources); // When - var searchResultModel = await this.searchService.Search(new ResourceSearchRequest("text", 0, 10)); + var searchResultModel = await this.searchService.Search(new ResourceSearchRequest("text", 0, 10), null); // Then searchResultModel.Resources.Select(r => r.ResourceId).Should().ContainInOrder(new[] { 1, 3, 2 }); @@ -194,7 +195,6 @@ public async Task SearchReplacesNullPropertiesOfResourceWithDefaultValues() { Builder.CreateNew() .With(r => r.Id = 1) - .With(r => r.CurrentResourceVersion = null) .With( r => r.ResourceReference = new[] { @@ -212,7 +212,7 @@ public async Task SearchReplacesNullPropertiesOfResourceWithDefaultValues() this.GivenFindwiseReturnsSuccessfulResponse(1, new[] { 1 }); // When - var searchResult = await this.searchService.Search(new ResourceSearchRequest("text", 0, 10)); + var searchResult = await this.searchService.Search(new ResourceSearchRequest("text", 0, 10), null); // Then using var scope = new AssertionScope(); @@ -233,7 +233,9 @@ public async Task SearchReplacesNullPropertiesOfResourceWithDefaultValues() string.Empty, expectedResourceReferences, "Article", - 0)); + 0, + 0, + new List(){ })); } private void GivenFindwiseReturnsSuccessfulResponse(int totalHits, IEnumerable resourceIds) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs new file mode 100644 index 000000000..863a8bb22 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs @@ -0,0 +1,41 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using System.Net; + using System.Security.Claims; + using LearningHub.Nhs.OpenApi.Models.Exceptions; + using Microsoft.AspNetCore.Mvc; + + /// + /// The base class for API controllers. + /// + public abstract class OpenApiControllerBase : ControllerBase + { + /// + /// Gets the current user's ID. + /// + public int? CurrentUserId + { + get + { + if ((this.User?.Identity?.AuthenticationType ?? null) == "AuthenticationTypes.Federation") + { + int userId; + if (int.TryParse(User.FindFirst(ClaimTypes.NameIdentifier).Value, out userId)) + { + return userId; + } + else + { + // If parsing fails, return null - for apikey this will be the name + return null; + } + } + else + { + // When authorizing by ApiKey we do not have a user for example + return null; + } + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index 95591961f..a92463d20 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -21,7 +21,7 @@ namespace LearningHub.NHS.OpenAPI.Controllers /// [Route("Resource")] [Authorize] - public class ResourceController : Controller + public class ResourceController : OpenApiControllerBase { private const int MaxNumberOfReferenceIds = 1000; private readonly ISearchService searchService; @@ -75,7 +75,7 @@ await this.searchService.Search( offset, limit ?? this.findwiseConfig.DefaultItemLimitForSearch, catalogueId, - resourceTypes)); + resourceTypes), this.CurrentUserId); switch (resourceSearchResult.FindwiseRequestStatus) { @@ -109,7 +109,7 @@ await this.searchService.Search( [HttpGet("{originalResourceReferenceId}")] public async Task GetResourceReferenceByOriginalId(int originalResourceReferenceId) { - return await this.resourceService.GetResourceReferenceByOriginalId(originalResourceReferenceId); + return await this.resourceService.GetResourceReferenceByOriginalId(originalResourceReferenceId, this.CurrentUserId); } /// @@ -125,7 +125,7 @@ public async Task GetResourceReferencesByOrigina throw new HttpResponseException($"Too many resources requested. The maximum is {MaxNumberOfReferenceIds}", HttpStatusCode.BadRequest); } - return await this.resourceService.GetResourceReferencesByOriginalIds(resourceReferenceIds.ToList()); + return await this.resourceService.GetResourceReferencesByOriginalIds(resourceReferenceIds.ToList(), this.CurrentUserId); } /// @@ -148,7 +148,7 @@ public async Task GetResourceReferencesByOrigina throw new HttpResponseException($"Too many resources requested. The maximum is {MaxNumberOfReferenceIds}", HttpStatusCode.BadRequest); } - return await this.resourceService.GetResourceReferencesByOriginalIds(bulkResourceReferences.ResourceReferenceIds); + return await this.resourceService.GetResourceReferencesByOriginalIds(bulkResourceReferences.ResourceReferenceIds, this.CurrentUserId); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user new file mode 100644 index 000000000..b17387f00 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj.user @@ -0,0 +1,9 @@ + + + + ProjectDebugger + + + IIS Local + + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index f513b6ec7..ee9f4c0ad 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -200,9 +200,6 @@ - - - @@ -518,10 +515,7 @@ - - - - + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetResourceActivityPerResourceMajorVersion.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetResourceActivityPerResourceMajorVersion.sql new file mode 100644 index 000000000..063110343 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Activity/GetResourceActivityPerResourceMajorVersion.sql @@ -0,0 +1,152 @@ + +------------------------------------------------------------------------------- +-- Author Phil T +-- Created 04-07-24 +-- Purpose Return resource activity for each major version for user + + +-- Description +/* + This procedure returns a single entry per resource Id, selecting the most important one for that major version. + This is so users can still have a resourceActivity history following a majorVersion change + + UserIds is nullable so that general resource activity can be searched for + ResourceIds is nullable so that all a users history can be searched for + + When determining the resourceActivity statusDescription in the front end resourceTypeId is also required for changing completed statuses to resourceType specific ones + + Currently if multiple rows meet the case criteria we retrieve the one with the highest Id which is also expected to be the ActivityEnd part of the activityStatus pair. + +*/ +-- Future Considerations +/* + Because the activityResource should come in pairs one with ActivityStart populated and one with ActivityEnd populated + it could be desireable to join via LaunchResourceActivityId and coalesce the data in future. + Or/And coalesce where the case returns multiple rows. + +*/ +-- Notes + -- resourceId is used not originalResourceId + + +------------------------------------------------------------------------------- + +-- Create the new stored procedure +CREATE PROCEDURE [activity].[GetResourceActivityPerResourceMajorVersion] + @ResourceIds VARCHAR(MAX) = NULL, + @UserIds VARCHAR(MAX) = NULL +AS +BEGIN + + -- Split the comma-separated list into a table of integers + DECLARE @ResourceIdTable TABLE (ResourceId INT); + + IF @ResourceIds IS NOT NULL AND @ResourceIds <> '' + BEGIN + INSERT INTO @ResourceIdTable (ResourceId) + SELECT CAST(value AS INT) + FROM STRING_SPLIT(@ResourceIds, ','); + END; + + -- Split the comma-separated list of UserIds into a table + DECLARE @UserIdTable TABLE (UserId INT); + + IF @UserIds IS NOT NULL AND @UserIds <> '' + BEGIN + INSERT INTO @UserIdTable (UserId) + SELECT CAST(value AS INT) + FROM STRING_SPLIT(@UserIds, ','); + END; + + WITH FilteredResourceActivities AS ( + SELECT + ars.[Id], + ars.[UserId], + ars.[LaunchResourceActivityId], + ars.[ResourceId], + ars.[ResourceVersionId], + ars.[MajorVersion], + ars.[MinorVersion], + ars.[NodePathId], + ars.[ActivityStatusId], + ars.[ActivityStart], + ars.[ActivityEnd], + ars.[DurationSeconds], + ars.[Score], + ars.[Deleted], + ars.[CreateUserID], + ars.[CreateDate], + ars.[AmendUserID], + ars.[AmendDate] + FROM + [activity].[resourceactivity] ars + WHERE + (@UserIds IS NULL OR ars.userId IN (SELECT UserId FROM @UserIdTable) OR NOT EXISTS (SELECT 1 FROM @UserIdTable)) + AND (@ResourceIds IS NULL OR @ResourceIds = '' OR ars.resourceId IN (SELECT ResourceId FROM @ResourceIdTable) OR NOT EXISTS (SELECT 1 FROM @ResourceIdTable)) + AND ars.Deleted = 0 + AND ars.ActivityStatusId NOT IN (1, 6, 2) -- These Ids are not in use - Launched, Downloaded, In Progress (stored as completed and incomplete then renamed in the application) + ), + RankedActivities AS ( + SELECT + ra.[Id], + ra.[UserId], + ra.[LaunchResourceActivityId], + ra.[ResourceId], + ra.[ResourceVersionId], + ra.[MajorVersion], + ra.[MinorVersion], + ra.[NodePathId], + ra.[ActivityStatusId], + ra.[ActivityStart], + ra.[ActivityEnd], + ra.[DurationSeconds], + ra.[Score], + ra.[Deleted], + ra.[CreateUserID], + ra.[CreateDate], + ra.[AmendUserID], + ra.[AmendDate], + ROW_NUMBER() OVER ( + PARTITION BY resourceId, userId, MajorVersion + ORDER BY + CASE + WHEN ActivityStatusId = 5 THEN 1 -- Passed + WHEN ActivityStatusId = 3 THEN 2 -- Completed + WHEN ActivityStatusId = 4 THEN 3 -- Failed + WHEN ActivityStatusId = 7 THEN 4 -- Incomplete + ELSE 5 -- shouldn't be any + END, + Id DESC -- we have two entries per interacting with a resource the start and the end, we are just returning the last entry made + -- there is the option of instead coalescing LaunchResourceActivityId, ActivityStart,ActivityEnd potentially via joining LaunchResourceActivityId and UserId + ) AS RowNum + FROM + FilteredResourceActivities ra + ) + SELECT + ra.[Id], + ra.[UserId], + ra.[LaunchResourceActivityId], + ra.[ResourceId], + ra.[ResourceVersionId], + ra.[MajorVersion], + ra.[MinorVersion], + ra.[NodePathId], + ra.[ActivityStatusId], + ra.[ActivityStart], + ra.[ActivityEnd], + ra.[DurationSeconds], + ra.[Score], + ra.[Deleted], + ra.[CreateUserID], + ra.[CreateDate], + ra.[AmendUserID], + ra.[AmendDate] + FROM + RankedActivities ra + WHERE + RowNum = 1 + order by MajorVersion desc; +END; +GO + + From d6c48109bd313dc59add5cae1ce1f699bb3955bf Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:48:52 +0100 Subject: [PATCH 05/29] Merge pull request #521 from TechnologyEnhancedLearning/MergeActivityStatus Merge activity status INto Dev --- .../Repositories/IResourceRepository.cs | 6 + .../Repositories/ResourceRepository.cs | 23 ++- .../Services/IResourceService.cs | 17 ++ .../Services/ResourceService.cs | 55 ++++++- .../Controllers/ResourceControllerTests.cs | 40 +++++ .../Services/Services/ResourceServiceTests.cs | 154 +++++++++++++++++- .../Controllers/OpenApiControllerBase.cs | 1 + .../Controllers/ResourceController.cs | 31 ++++ .../SwaggerDefinitions/v1.3.0.json | 46 ++++++ .../LearningHub.Nhs.Database.sqlproj | 6 + ...ficatedResourcesWithOptionalPagination.sql | 118 ++++++++++++++ .../GetMyCertificatesDashboardResources.sql | 79 +-------- 12 files changed, 495 insertions(+), 81 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetAchievedCertificatedResourcesWithOptionalPagination.sql diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs index d3a1856e4..52b3dee08 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs @@ -32,5 +32,11 @@ public Task> GetResourceReferencesByOriginalResou /// /// ResourceActivityDTO. Task> GetResourceActivityPerResourceMajorVersion(IEnumerable? resourceReferenceIds, IEnumerable? userIds); + + /// + /// GetAchievedCertificatedResourceIds + /// + /// . + public Task> GetAchievedCertificatedResourceIds(int currentUserId); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs index 7ffb3c6fa..79b7a239b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs @@ -2,8 +2,12 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories { using System; using System.Collections.Generic; + using System.ComponentModel; + using System.Data; using System.Linq; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; @@ -73,16 +77,29 @@ public async Task> GetResourceReferencesByOrigina .ToListAsync(); } + /// + public async Task> GetAchievedCertificatedResourceIds(int currentUserId) + { + // Use dashboard logic to ensure same resources determined has having achieved certificates + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = currentUserId }; + var param4 = new SqlParameter("@TotalRecords", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + var result = this.dbContext.DashboardResourceDto.FromSqlRaw("resources.GetAchievedCertificatedResourcesWithOptionalPagination @userId = @userId, @TotalRecords = @TotalRecords output", param0, param4).ToList(); + List achievedCertificatedResourceIds = result.Select(drd => drd.ResourceId).Distinct().ToList(); + + return achievedCertificatedResourceIds; + } + /// /// /// /// . /// A representing the result of the asynchronous operation. public async Task> GetResourceActivityPerResourceMajorVersion( - IEnumerable? resourceReferenceIds, IEnumerable? userIds) + IEnumerable? resourceIds, IEnumerable? userIds) { - var resourceIdsParam = resourceReferenceIds != null - ? string.Join(",", resourceReferenceIds) + var resourceIdsParam = resourceIds != null + ? string.Join(",", resourceIds) : null; var userIdsParam = userIds != null diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index 795ddbf42..f5f59cb1d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -10,17 +10,34 @@ namespace LearningHub.Nhs.OpenApi.Services.Interface.Services /// public interface IResourceService { + /// + /// The get resource by activityStatusIds async. + /// + /// activityStatusIds. + /// c. + /// The the resourceMetaDataViewModel corresponding to the resource reference. + Task> GetResourceReferenceByActivityStatus(List activityStatusIds, int currentUserId); + /// /// The get resource by id async. /// /// The original resource reference id. + /// . /// The the resourceMetaDataViewModel corresponding to the resource reference. Task GetResourceReferenceByOriginalId(int originalResourceReferenceId, int? currentUserId); + /// + /// The get resource references for certificates + /// + /// currentUserId. + /// The ResourceReferenceWithResourceDetailsViewModelthe resourceMetaDataViewModel corresponding to the resource reference. + Task> GetResourceReferencesForCertificates(int currentUserId); + /// /// The get resources by Ids endpoint. /// /// The original resource reference Ids. + /// . /// The resourceReferenceMetaDataViewModel. Task GetResourceReferencesByOriginalIds(List originalResourceReferenceIds, int? currentUserId); } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index 3212b0b37..4848cc1e6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -8,6 +8,7 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using System.Threading.Tasks; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.ViewModels.Helpers; using LearningHub.Nhs.OpenApi.Models.Exceptions; using LearningHub.Nhs.OpenApi.Models.ViewModels; @@ -50,7 +51,7 @@ public ResourceService(ILearningHubService learningHubService, IResourceReposito /// the get by id async. /// /// the id. - /// + /// . /// the resource. public async Task GetResourceReferenceByOriginalId(int originalResourceReferenceId, int? currentUserId) { @@ -126,6 +127,58 @@ public async Task GetResourceReferencesByOrigina return new BulkResourceReferenceViewModel(matchedResources, unmatchedIds); } + + /// + /// the get by id async. + /// + /// . + /// c. + /// list resource ViewModel. + public async Task> GetResourceReferenceByActivityStatus(List activityStatusIds, int currentUserId) + { + List resourceActivities = new List() { }; + List resourceReferenceWithResourceDetailsViewModelLS = new List() { }; + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(new List(){ }, new List(){ currentUserId }))?.ToList() ?? new List() { }; + + // Removing resources that have no major versions with the required activitystatus + List resourceIds = resourceActivities + .GroupBy(ra => ra.ResourceId) + .Where(group => group.Any(g => activityStatusIds.Contains(g.ActivityStatusId))) + .Select(group => group.Key) + .Distinct() + .ToList(); + + var resourceReferencesList = (await this.resourceRepository.GetResourcesFromIds(resourceIds)).SelectMany(r => r.ResourceReference).ToList(); + + resourceReferenceWithResourceDetailsViewModelLS = resourceReferencesList.Select(rr => this.GetResourceReferenceWithResourceDetailsViewModel(rr, resourceActivities)).ToList(); + + return resourceReferenceWithResourceDetailsViewModelLS; + } + + /// + /// Gets ResourceReferences ForCertificates using the ResourceReferenceWithResourceDetailsViewModel . + /// + /// user Id. + /// list resource reference ViewModel. + public async Task> GetResourceReferencesForCertificates(int currentUserId) + { + + List resourceActivities = new List() { }; + List resourceReferenceWithResourceDetailsViewModelLS = new List() { }; + List achievedCertificatedResourceIds = (await this.resourceRepository.GetAchievedCertificatedResourceIds(currentUserId)).ToList(); + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(achievedCertificatedResourceIds, new List() { currentUserId }))?.ToList() ?? new List() { }; + + var resourceList = (await this.resourceRepository.GetResourcesFromIds(achievedCertificatedResourceIds)).ToList(); + + resourceReferenceWithResourceDetailsViewModelLS = resourceList.SelectMany(r => r.ResourceReference) + .Distinct() + .Select(rr => this.GetResourceReferenceWithResourceDetailsViewModel(rr, resourceActivities)).ToList(); + + return resourceReferenceWithResourceDetailsViewModelLS; + } + private ResourceReferenceWithResourceDetailsViewModel GetResourceReferenceWithResourceDetailsViewModel(ResourceReference resourceReference, List resourceActivities) { var hasCurrentResourceVersion = resourceReference.Resource.CurrentResourceVersion != null; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs index acde1d3ef..d0126a840 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Controllers/ResourceControllerTests.cs @@ -21,6 +21,7 @@ namespace LearningHub.Nhs.OpenApi.Tests.Controllers using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; + using LearningHub.Nhs.Models.Enums; public sealed class ResourceControllerTests { @@ -239,6 +240,45 @@ public async Task SearchEndpointUsesPassedInLimitIfGiven(int limit) service => service.Search(It.Is(request => request.Limit == limit), currentUserId)); } + [Fact] + public async Task GetResourceReferencesByCompleteThrowsErrorWhenNoUserId() + { + // When + var exception = await Assert.ThrowsAsync(async () => + { + await this.resourceController.GetResourceReferencesByActivityStatus((int)ActivityStatusEnum.Completed); + }); + + // Then + Assert.Equal("User Id required.", exception.Message); + } + + [Fact] + public async Task GetResourceReferencesByInProgressThrowsErrorWhenNoUserId() + { + // When + var exception = await Assert.ThrowsAsync(async () => + { + await this.resourceController.GetResourceReferencesByActivityStatus((int)ActivityStatusEnum.Incomplete);// in complete in db is in progress front endS + }); + + // Then + Assert.Equal("User Id required.", exception.Message); + } + + [Fact] + public async Task GetResourceReferencesBycertificatesThrowsErrorWhenNoUserId() + { + // When + var exception = await Assert.ThrowsAsync(async () => + { + await this.resourceController.GetResourceReferencesByCertificates(); + }); + + // Then + Assert.Equal("User Id required.", exception.Message); + } + private void GivenDefaultLimitForFindwiseSearchIs(int limit) { this.findwiseConfigOptions.Setup(options => options.Value) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs index 1f791a353..ddddc6ebc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs @@ -28,13 +28,14 @@ public class ResourceServiceTests public ResourceServiceTests() { - //This Id is the development accountId + // This Id is the development accountId this.currentUserId = 57541; this.learningHubService = new Mock(); this.resourceRepository = new Mock(); this.resourceService = new ResourceService(this.learningHubService.Object, this.resourceRepository.Object, new NullLogger()); } + private List ResourceActivityDTOList => new List() { new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 5, MajorVersion = 5 }, @@ -42,7 +43,16 @@ public ResourceServiceTests() new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 3, MajorVersion = 3 }, new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 7, MajorVersion = 2 }, new ResourceActivityDTO{ ResourceId = 1, ActivityStatusId = 3, MajorVersion = 1 }, + + new ResourceActivityDTO{ ResourceId = 2, ActivityStatusId = 5, MajorVersion = 5 }, // Passed + new ResourceActivityDTO{ ResourceId = 2, ActivityStatusId = 4, MajorVersion = 4 }, // Failed + new ResourceActivityDTO{ ResourceId = 2, ActivityStatusId = 3, MajorVersion = 3 }, // complete + + new ResourceActivityDTO{ ResourceId = 3, ActivityStatusId = 4, MajorVersion = 2 }, // Failed + new ResourceActivityDTO{ ResourceId = 3, ActivityStatusId = 4, MajorVersion = 1 }, // Failed + new ResourceActivityDTO{ ResourceId = 3, ActivityStatusId = 7, MajorVersion = 4 }, // In complete }; + private List ResourceList => new List() { ResourceTestHelper.CreateResourceWithDetails(id: 1, title: "title1", description: "description1", rating: 3m, resourceType: ResourceTypeEnum.Article), @@ -305,12 +315,12 @@ public async Task ResourceServiceReturnsTheOriginalResourceReferenceIdAsTheRefId this.resourceRepository.Setup(rr => rr.GetResourceReferencesByOriginalResourceReferenceIds(list)) .ReturnsAsync(this.ResourceReferenceList.GetRange(5, 1)); - // When + // When var x = await this.resourceService.GetResourceReferenceByOriginalId(6, null); - // Then + // Then x.RefId.Should().Be(6); - } + } [Fact] public async Task ResourceServiceReturnsThatARestrictedCatalogueIsRestricted() @@ -368,7 +378,6 @@ public async Task SingleResourceEndpointReturnsActivitySummaryWhenCurrentUserIdP x.UserSummaryActivityStatuses[2].ActivityStatusDescription.Should().Be("Viewed"); x.UserSummaryActivityStatuses[3].ActivityStatusDescription.Should().Be("In progress"); x.UserSummaryActivityStatuses[4].ActivityStatusDescription.Should().Be("Viewed"); - } [Fact] @@ -387,7 +396,142 @@ public async Task SingleResourceEndpointReturnsEmptyActivitySummaryWhenNoCurrent // Then x.UserSummaryActivityStatuses.Should().BeEmpty(); + } + + [Fact] + public async Task GetResourceReferencesByCompleteReturnsCorrectInformation() + { + // Given + List resourceIds = new List() { 1, 2 }; + List resources = this.ResourceList.GetRange(0, 2); + resources[0].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 1, title: "title1", description: "description1", rating: 3m, resourceType: ResourceTypeEnum.Article); + resources[1].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 2, hasCurrentResourceVersion: false, hasNodePath: false, resourceType: ResourceTypeEnum.Assessment); + + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(It.IsAny>(), It.IsAny>())) + .ReturnsAsync(this.ResourceActivityDTOList); + + this.resourceRepository.Setup(rr => rr.GetResourcesFromIds(resourceIds)) + .ReturnsAsync(resources); + + // When + var x = await this.resourceService.GetResourceReferenceByActivityStatus(new List() { (int)ActivityStatusEnum.Completed }, currentUserId); + + // Then + + // Two groups resourceId 1 and 2 have completed for a major version. ResourceId 3 had resourceActivity data but not completed + x.Count().Should().Be(2); + + // We are including all the major versions not just the matching ones if there exists one matching one + x[0].ResourceId.Should().Be(1); + x[0].UserSummaryActivityStatuses.Count().Should().Be(5); + + // Return all the activitySummaries if one match + x[1].ResourceId.Should().Be(2); + x[1].UserSummaryActivityStatuses.Count().Should().Be(3); + + // we are not excluding major version that are not completed. We return the resource and all its activitySummaries if one matches + x[0].UserSummaryActivityStatuses[1].ActivityStatusDescription.Should().Be("In progress"); + x[0].UserSummaryActivityStatuses[2].ActivityStatusDescription.Should().Be("Viewed"); // Rename completed and still return it + + } + + [Fact] + public async Task GetResourceReferencesByInProgressReturnsCorrectInformation() + { + // Given + List resourceIds = new List() { 1, 3 }; + List resources = new List() { this.ResourceList[0], this.ResourceList[2] }; + resources[0].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 1, title: "title1", description: "description1", rating: 3m, resourceType: ResourceTypeEnum.Article); + resources[1].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 3, title: "title2", description: "description2"); + + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(It.IsAny>(), It.IsAny>())) + .ReturnsAsync(this.ResourceActivityDTOList); + + this.resourceRepository.Setup(rr => rr.GetResourcesFromIds(resourceIds)) + .ReturnsAsync(resources); + + // When + var x = await this.resourceService.GetResourceReferenceByActivityStatus(new List() { (int)ActivityStatusEnum.Incomplete }, currentUserId); // In complete in the database is in progress im database + + // Then + // Two groups resourceId 1 and 3 have completed for a major version. ResourceId 2 had resourceActivity data but not "in progress" + x.Count().Should().Be(2); + + // We are including all the major versions not just the matching ones if there exists one matching one + x[0].ResourceId.Should().Be(1); + x[0].UserSummaryActivityStatuses.Count().Should().Be(5); + + // Return all the activitySummaries if one match + x[1].ResourceId.Should().Be(3); + x[1].UserSummaryActivityStatuses.Count().Should().Be(3); + + // we are not excluding major version that are not completed. We return the resource and all its activitySummaries if one matches + x[0].UserSummaryActivityStatuses[1].ActivityStatusDescription.Should().Be("In progress"); + x[0].UserSummaryActivityStatuses[2].ActivityStatusDescription.Should().Be("Viewed"); // Rename completed and still return it + + } + + [Fact] + public async Task GetResourceReferencesByCertificatesReturnsCorrectInformation() + { + + // Given + List resourceIds = new List() { 1, 3 }; // Ids returned from activity + + List resources = new List() { this.ResourceList[0], this.ResourceList[2] }; + resources[0].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 1, title: "title1", description: "description1", rating: 3m, resourceType: ResourceTypeEnum.Article); + resources[1].ResourceReference.ToList()[0].Resource = ResourceTestHelper.CreateResourceWithDetails(id: 3, title: "title2", description: "description2"); + + + // Will be passed resourceIds and currentUserId + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(It.IsAny>(), It.IsAny>())) + .ReturnsAsync(this.ResourceActivityDTOList); + + this.resourceRepository.Setup(rr => rr.GetAchievedCertificatedResourceIds(currentUserId)) + .ReturnsAsync(resourceIds); + + this.resourceRepository.Setup(rr => rr.GetResourcesFromIds(resourceIds)) + .ReturnsAsync(resources); + + // When + var x = await this.resourceService.GetResourceReferencesForCertificates(currentUserId); + + // Then + + x.Count().Should().Be(2); + + // We are including all the major versions not just the matching ones if there exists one matching one + x[0].ResourceId.Should().Be(1); + x[0].UserSummaryActivityStatuses.Count().Should().Be(5); + + // Return all the activitySummaries if one match + x[1].ResourceId.Should().Be(3); + x[1].UserSummaryActivityStatuses.Count().Should().Be(3); + + // we are not excluding major version that are not completed (assuming here that its completed and has certificated flag). We return the resource and all its activitySummaries if one matches + x[0].UserSummaryActivityStatuses[1].ActivityStatusDescription.Should().Be("In progress"); + x[0].UserSummaryActivityStatuses[2].ActivityStatusDescription.Should().Be("Viewed"); // Rename completed and still return it + } + + [Fact] + public async Task GetResourceReferencesByCompleteNoActivitySummaryFound() + { + // Given + List resourceIds = new List() { }; + List resources = this.ResourceList.GetRange(0, 0); + + this.resourceRepository.Setup(rr => rr.GetResourceActivityPerResourceMajorVersion(It.IsAny>(), It.IsAny>())) + .ReturnsAsync(this.ResourceActivityDTOList.GetRange(8, 3)); + + this.resourceRepository.Setup(rr => rr.GetResourcesFromIds(resourceIds)) + .ReturnsAsync(resources); + + // When + var x = await this.resourceService.GetResourceReferenceByActivityStatus(new List() { (int)ActivityStatusEnum.Completed }, currentUserId); + + // Then + x.Count().Should().Be(0); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs index 863a8bb22..799c15190 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs @@ -17,6 +17,7 @@ public int? CurrentUserId { get { + // This check is to determine between the two ways of authorising, OAuth and APIKey.OAuth provides userId and APIKey does not. For OpenApi we provide the data without specific user info. if ((this.User?.Identity?.AuthenticationType ?? null) == "AuthenticationTypes.Federation") { int userId; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index a92463d20..c48aa4f83 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -1,10 +1,12 @@ namespace LearningHub.NHS.OpenAPI.Controllers { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Models.Exceptions; using LearningHub.Nhs.OpenApi.Models.ServiceModels.Findwise; @@ -150,5 +152,34 @@ public async Task GetResourceReferencesByOrigina return await this.resourceService.GetResourceReferencesByOriginalIds(bulkResourceReferences.ResourceReferenceIds, this.CurrentUserId); } + + /// + /// Get resourceReferences that have an in progress activity summary + /// + /// ResourceReferenceViewModels for matching resources. + [HttpGet("User/{activityStatusId}")] + public async Task> GetResourceReferencesByActivityStatus(int activityStatusId) + { + // These activity statuses are set with other activity statuses and resource type within the ActivityStatusHelper.GetActivityStatusDescription + // Note In progress is in complete in the db + List activityStatusIdsNotInUseInDB = new List() { (int)ActivityStatusEnum.Launched, (int)ActivityStatusEnum.InProgress, (int)ActivityStatusEnum.Viewed, (int)ActivityStatusEnum.Downloaded }; + if (this.CurrentUserId == null) throw new UnauthorizedAccessException("User Id required."); + if (!Enum.IsDefined(typeof(ActivityStatusEnum), activityStatusId)) throw new ArgumentOutOfRangeException($"activityStatusId : {activityStatusId} does not exist within ActivityStatusEnum"); + if (activityStatusIdsNotInUseInDB.Contains(activityStatusId)) throw new ArgumentOutOfRangeException($"activityStatusId: {activityStatusId} does not exist within the database definitions"); + + return await this.resourceService.GetResourceReferenceByActivityStatus(new List() { activityStatusId }, this.CurrentUserId.Value); + } + + /// + /// Get resourceReferences that have certificates + /// + /// ResourceReferenceViewModels for matching resources. + [HttpGet("User/Certificates")] + public async Task> GetResourceReferencesByCertificates() + { + if (this.CurrentUserId == null) throw new UnauthorizedAccessException("User Id required."); + + return await this.resourceService.GetResourceReferencesForCertificates(this.CurrentUserId.Value); + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index d77f6b823..e7644d565 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -295,6 +295,52 @@ } } } + }, + "/Resource/ActivityStatus/{activityStatusId}": { + "get": { + "tags": [ "Resource" ], + "summary": "Get resource references by activity status", + "operationId": "GetResourceReferencesByActivityStatus", + "parameters": [ + { + "name": "activityStatusId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "description": "The activity status Id to filter resource references. Valid values are Completed 3 (returned as Completed/Downloaded/Launched/Viewed), Incomplete 7 (returned as In progress), Passed 5, Failed 4." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceReferenceWithResourceDetailsViewModel" + } + } + } + } + }, + "400": { + "description": "Bad request: The activityStatusId provided is not valid." + }, + "401": { + "description": "Unauthorized: User Id required." + }, + "403": { + "description": "Forbidden: The activityStatusId is not defined within ActivityStatusEnum or is in the list of activityStatusIdsNotInUseInDB." + }, + "500": { + "description": "Internal server error: An unexpected error occurred while processing the request." + } + } + } } }, "components": { diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index ee9f4c0ad..39428b822 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -516,6 +516,12 @@ + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetAchievedCertificatedResourcesWithOptionalPagination.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetAchievedCertificatedResourcesWithOptionalPagination.sql new file mode 100644 index 000000000..922480858 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetAchievedCertificatedResourcesWithOptionalPagination.sql @@ -0,0 +1,118 @@ + +------------------------------------------------------------------------------- +-- Author PT +-- Created 11 July 2024 +-- Purpose Get achieved certificated resources with optional pagination +-- Description Extracted from the GetDashboardResources sproc to enable one source of truth for determining achieved certificated resources +-- To support the GetDashboardResources it has pagination and to support other requests the default values disable pagination effects +------------------------------------------------------------------------------- + + + +CREATE PROCEDURE [resources].[GetAchievedcertificatedResourcesWithOptionalPagination] + @UserId INT, + + -- Default values disable pagination + @MaxRows INT = 2147483647, -- Warning! Magic number. To disable pagination by default. + @OffsetRows INT = 0, + @FetchRows INT = 2147483647, -- Warning! Magic number. To disable pagination by default. + + @TotalRecords INT OUTPUT +AS +BEGIN + + -- Step 1: Create a table variable to store intermediate results + DECLARE @MyActivity TABLE ( + ResourceId INT, + ResourceActivityId INT + ); + + INSERT INTO @MyActivity + SELECT TOP (@MaxRows) ra.ResourceId, MAX(ra.Id) ResourceActivityId + FROM + /* resources with resource activity, resource activity determines if certificated*/ + activity.ResourceActivity ra + JOIN [resources].[Resource] r ON ra.ResourceId = r.Id + JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId + + /* Determining if certificated scorm, assessment mark, media*/ + LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId + LEFT JOIN [activity].[AssessmentResourceActivity] ara ON ara.ResourceActivityId = ra.Id + LEFT JOIN [activity].[MediaResourceActivity] mar ON mar.ResourceActivityId = ra.Id + LEFT JOIN [activity].[ScormActivity] sa ON sa.ResourceActivityId = ra.Id + + WHERE ra.UserId = @UserId AND rv.CertificateEnabled = 1 -- detemining if certificated + AND ( + (r.ResourceTypeId IN (2, 7) AND ra.ActivityStatusId IN (3) /* resourceType 2 Audio and 7 is video activityStatusId 3 is completed */ + OR ra.ActivityStart < '2020-09-07 00:00:00 +00:00' /* old activity assumed to be valid*/ + OR mar.Id IS NOT NULL AND mar.PercentComplete = 100 /* media activity 100% complete*/ + ) + /* type 6 scorm elearning,*/ + OR (r.ResourceTypeId = 6 AND (sa.CmiCoreLesson_status IN(3,5) OR (ra.ActivityStatusId IN(3, 5)))) /* activityStatus 3 and 5 are completed and passed */ + /* 11 is assessment */ + OR (r.ResourceTypeId = 11 AND ara.Score >= arv.PassMark OR ra.ActivityStatusId IN(3, 5)) /*assessment mark and activity status passed completed */ + /* 1 Article, 5 Image, 8 Weblink 9 file, 10 case, 12 html */ + OR (r.ResourceTypeId IN (1, 5, 8, 9, 10, 12) AND ra.ActivityStatusId IN (3))) /* Completed */ + GROUP BY ra.ResourceId + ORDER BY ResourceActivityId DESC + + SELECT r.Id AS ResourceId + ,( SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0 + ) AS ResourceReferenceID + ,r.CurrentResourceVersionId AS ResourceVersionId + ,r.ResourceTypeId AS ResourceTypeId + ,rv.Title + ,rv.Description + ,CASE + WHEN r.ResourceTypeId = 7 THEN + (SELECT vrv.DurationInMilliseconds from [resources].[VideoResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) + WHEN r.ResourceTypeId = 2 THEN + (SELECT vrv.DurationInMilliseconds from [resources].[AudioResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) + ELSE + NULL + END AS DurationInMilliseconds + ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.Name END AS CatalogueName + ,cnv.Url AS Url + ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.BadgeUrl END AS BadgeUrl + ,cnv.RestrictedAccess + ,CAST(CASE WHEN cnv.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 ELSE 1 END AS bit) AS HasAccess + ,ub.Id AS BookMarkId + ,CAST(ISNULL(ub.[Deleted], 1) ^ 1 AS BIT) AS IsBookmarked + ,rs.AverageRating + ,rs.RatingCount + FROM @MyActivity ma + JOIN activity.ResourceActivity ra ON ra.id = ma.ResourceActivityId + JOIN resources.resourceversion rv ON rv.id = ra.ResourceVersionId AND rv.Deleted = 0 + JOIN Resources.Resource r ON r.Id = rv.ResourceId + JOIN hierarchy.Publication p ON rv.PublicationId = p.Id AND p.Deleted = 0 + JOIN resources.ResourceVersionRatingSummary rvrs ON rv.Id = rvrs.ResourceVersionId AND rvrs.Deleted = 0 + + /* Catalogue logic */ + JOIN hierarchy.NodeResource nr ON r.Id = nr.ResourceId AND nr.Deleted = 0 + JOIN hierarchy.Node n ON n.Id = nr.NodeId AND n.Hidden = 0 AND n.Deleted = 0 + JOIN hierarchy.NodePath np ON np.NodeId = n.Id AND np.Deleted = 0 AND np.IsActive = 1 + JOIN hierarchy.NodeVersion nv ON nv.NodeId = np.CatalogueNodeId AND nv.VersionStatusId = 2 AND nv.Deleted = 0 + JOIN hierarchy.CatalogueNodeVersion cnv ON cnv.NodeVersionId = nv.Id AND cnv.Deleted = 0 + + /* Book marks */ + LEFT JOIN hub.UserBookmark ub ON ub.UserId = @UserId AND ub.ResourceReferenceId = (SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0) + LEFT JOIN ( SELECT DISTINCT CatalogueNodeId + FROM [hub].[RoleUserGroupView] rug JOIN hub.UserUserGroup uug ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 and rug.RoleId in (1,2,3) and uug.Deleted = 0 and uug.UserId = @userId) auth ON n.Id = auth.CatalogueNodeId + LEFT JOIN resources.ResourceVersionRatingSummary rs ON rs.ResourceVersionId = rv.Id + ORDER BY ma.ResourceActivityId DESC, rv.Title + + /* pagination logic */ + OFFSET @OffsetRows ROWS + FETCH NEXT @FetchRows ROWS ONLY + SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END FROM @MyActivity + END; +GO + + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyCertificatesDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyCertificatesDashboardResources.sql index b75e45860..7beec0594 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyCertificatesDashboardResources.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyCertificatesDashboardResources.sql @@ -6,6 +6,7 @@ -- Modification History -- -- 24 Jun 2024 OA Initial Revision +-- 31 Jun 2024 PT Extracting functionality of certification with optional pagination so can be used on openapi and be single source of truth ------------------------------------------------------------------------------- CREATE PROCEDURE [resources].[GetMyCertificatesDashboardResources] @@ -25,77 +26,11 @@ BEGIN DECLARE @MaxRows INT = @MaxPageNUmber * @FetchRows DECLARE @OffsetRows INT = (@PageNumber - 1) * @FetchRows - DECLARE @MyActivity TABLE (ResourceId [int] NOT NULL PRIMARY KEY, ResourceActivityId [int] NOT NULL); - DECLARE @Resources TABLE (ResourceId [int] NOT NULL PRIMARY KEY, ResourceActivityCount [int] NOT NULL); + EXEC [resources].[GetAchievedcertificatedResourcesWithOptionalPagination] + @UserId = @UserId, + @MaxRows= @MaxRows, + @OffsetRows = @OffsetRows, + @FetchRows = @FetchRows, + @TotalRecords = @TotalRecords; - INSERT INTO @MyActivity - SELECT TOP (@MaxRows) ra.ResourceId, MAX(ra.Id) ResourceActivityId - FROM - activity.ResourceActivity ra - JOIN [resources].[Resource] r ON ra.ResourceId = r.Id - JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId - LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId - LEFT JOIN [activity].[AssessmentResourceActivity] ara ON ara.ResourceActivityId = ra.Id - LEFT JOIN [activity].[MediaResourceActivity] mar ON mar.ResourceActivityId = ra.Id - LEFT JOIN [activity].[ScormActivity] sa ON sa.ResourceActivityId = ra.Id - WHERE ra.UserId = @UserId AND rv.CertificateEnabled = 1 - AND ( - (r.ResourceTypeId IN (2, 7) AND ra.ActivityStatusId = 3 OR ra.ActivityStart < '2020-09-07 00:00:00 +00:00' OR mar.Id IS NOT NULL AND mar.PercentComplete = 100) - OR (r.ResourceTypeId = 6 AND (sa.CmiCoreLesson_status IN(3,5) OR (ra.ActivityStatusId IN(3, 5)))) - OR ((r.ResourceTypeId = 11 AND arv.AssessmentType = 2) AND (ara.Score >= arv.PassMark OR ra.ActivityStatusId IN(3, 5))) - OR ((r.ResourceTypeId = 11 AND arv.AssessmentType =1) AND (ara.Score >= arv.PassMark AND ra.ActivityStatusId IN(3, 5,7))) - OR (r.ResourceTypeId IN (1, 5, 8, 9, 10, 12) AND ra.ActivityStatusId = 3)) - GROUP BY ra.ResourceId - ORDER BY ResourceActivityId DESC - - SELECT r.Id AS ResourceId - ,( SELECT TOP 1 rr.OriginalResourceReferenceId - FROM [resources].[ResourceReference] rr - JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 - WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0 - ) AS ResourceReferenceID - ,r.CurrentResourceVersionId AS ResourceVersionId - ,r.ResourceTypeId AS ResourceTypeId - ,rv.Title - ,rv.Description - ,CASE - WHEN r.ResourceTypeId = 7 THEN - (SELECT vrv.DurationInMilliseconds from [resources].[VideoResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) - WHEN r.ResourceTypeId = 2 THEN - (SELECT vrv.DurationInMilliseconds from [resources].[AudioResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) - ELSE - NULL - END AS DurationInMilliseconds - ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.Name END AS CatalogueName - ,cnv.Url AS Url - ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.BadgeUrl END AS BadgeUrl - ,cnv.RestrictedAccess - ,CAST(CASE WHEN cnv.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 ELSE 1 END AS bit) AS HasAccess - ,ub.Id AS BookMarkId - ,CAST(ISNULL(ub.[Deleted], 1) ^ 1 AS BIT) AS IsBookmarked - ,rvrs.AverageRating - ,rvrs.RatingCount -FROM @MyActivity ma -JOIN activity.ResourceActivity ra ON ra.id = ma.ResourceActivityId -JOIN resources.resourceversion rv ON rv.id = ra.ResourceVersionId AND rv.Deleted = 0 -JOIN Resources.Resource r ON r.Id = rv.ResourceId -JOIN hierarchy.Publication p ON rv.PublicationId = p.Id AND p.Deleted = 0 -JOIN resources.ResourceVersionRatingSummary rvrs ON rv.Id = rvrs.ResourceVersionId AND rvrs.Deleted = 0 -JOIN hierarchy.NodeResource nr ON r.Id = nr.ResourceId AND nr.Deleted = 0 -JOIN hierarchy.Node n ON n.Id = nr.NodeId AND n.Hidden = 0 AND n.Deleted = 0 -JOIN hierarchy.NodePath np ON np.NodeId = n.Id AND np.Deleted = 0 AND np.IsActive = 1 -JOIN hierarchy.NodeVersion nv ON nv.NodeId = np.CatalogueNodeId AND nv.VersionStatusId = 2 AND nv.Deleted = 0 -JOIN hierarchy.CatalogueNodeVersion cnv ON cnv.NodeVersionId = nv.Id AND cnv.Deleted = 0 -LEFT JOIN hub.UserBookmark ub ON ub.UserId = @UserId AND ub.ResourceReferenceId = (SELECT TOP 1 rr.OriginalResourceReferenceId - FROM [resources].[ResourceReference] rr - JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 - WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0) -LEFT JOIN ( SELECT DISTINCT CatalogueNodeId - FROM [hub].[RoleUserGroupView] rug JOIN hub.UserUserGroup uug ON rug.UserGroupId = uug.UserGroupId - WHERE rug.ScopeTypeId = 1 and rug.RoleId in (1,2,3) and uug.Deleted = 0 and uug.UserId = @userId) auth ON n.Id = auth.CatalogueNodeId -ORDER BY ma.ResourceActivityId DESC, rv.Title -OFFSET @OffsetRows ROWS -FETCH NEXT @FetchRows ROWS ONLY - - SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END FROM @MyActivity END \ No newline at end of file From fb5ab1efcfc9aa783553ccac7c0ae0c6510d7dad Mon Sep 17 00:00:00 2001 From: Phil <165780796+Phil-NHS@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:07:59 +0100 Subject: [PATCH 06/29] Merge pull request #522 from TechnologyEnhancedLearning/Develop/Feature/TD-3678-swagger-version-json-update Swagger file was missing certificates endpoint --- .gitignore | 2 ++ .../Controllers/BookmarkController.cs | 9 ++--- .../Controllers/OpenApiControllerBase.cs | 18 ++++++++++ .../SwaggerDefinitions/v1.3.0.json | 33 +++++++++++++++++-- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 04baa838f..5e8ca2030 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ obj /LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.Development.json /OpenAPI/LearningHub.Nhs.OpenApi/appsettings.Development.json /OpenAPI/LearningHub.Nhs.OpenApi/web.config +/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user +/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs index d5897aa2a..9c7d8f0b9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs @@ -15,7 +15,7 @@ [Authorize] [Route("Bookmark")] [ApiController] - public class BookmarkController : Controller + public class BookmarkController : OpenApiControllerBase { private readonly IBookmarkService bookmarkService; @@ -28,6 +28,7 @@ public BookmarkController(IBookmarkService bookmarkService) this.bookmarkService = bookmarkService; } + /// /// /// Gets all bookmarks by parent. /// @@ -36,11 +37,7 @@ public BookmarkController(IBookmarkService bookmarkService) [Route("GetAllByParent")] public async Task> GetAllByParent() { - var accessToken = await this.HttpContext - .GetTokenAsync(OpenIdConnectParameterNames.AccessToken); - - return await this.bookmarkService.GetAllByParent( - accessToken); + return await this.bookmarkService.GetAllByParent(this.TokenWithoutBearer); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs index 799c15190..16fd3799c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/OpenApiControllerBase.cs @@ -38,5 +38,23 @@ public int? CurrentUserId } } } + + /// + /// Gets the bearer token from OAuth and removes "Bearer " prepend. + /// + public string TokenWithoutBearer + { + get + { + string accessToken = this.HttpContext.Request.Headers["Authorization"].ToString(); + + if (string.IsNullOrEmpty(accessToken)) + { + throw new HttpResponseException($"No token provided please use OAuth", HttpStatusCode.Unauthorized); + } + + return accessToken.StartsWith("Bearer ") ? accessToken.Substring("Bearer ".Length) : accessToken; + } + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index e7644d565..4fdbbba8b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.1", + "openapi": "3.0.2", "info": { "title": "LearningHub.NHS.OpenAPI", "version": "1.3.0", @@ -296,7 +296,7 @@ } } }, - "/Resource/ActivityStatus/{activityStatusId}": { + "/Resource/User/{activityStatusId}": { "get": { "tags": [ "Resource" ], "summary": "Get resource references by activity status", @@ -341,6 +341,35 @@ } } } + }, + "/Resource/User/Certificates": { + "get": { + "tags": [ "Resource" ], + "summary": "Get resource references where a major version has a certificate", + "operationId": "GetResourceReferencesByCertificates", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceReferenceWithResourceDetailsViewModel" + } + } + } + } + }, + "401": { + "description": "Unauthorized: User Id required." + }, + "500": { + "description": "Internal server error: An unexpected error occurred while processing the request." + } + } + } } }, "components": { From bdc69c6be4247813ddacb7d56757d2d470817201 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Mon, 2 Sep 2024 12:14:33 +0100 Subject: [PATCH 07/29] TD-4283: Fixed download issue for elearning resource in firefox browser --- .../Controllers/LearningSessionsController.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/LearningSessionsController.cs b/LearningHub.Nhs.WebUI/Controllers/LearningSessionsController.cs index 4f77c00bb..755c14d62 100644 --- a/LearningHub.Nhs.WebUI/Controllers/LearningSessionsController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/LearningSessionsController.cs @@ -68,6 +68,7 @@ public async Task Scorm(int id) /// filePath. /// bool. //// [ResponseCache(VaryByQueryKeys = new[] { "*" }, Duration = 0, NoStore = true)] // disable caching + //// Removed Request.Headers["Referer"] Referer URL checking based on issue reported in TD-4283 [AllowAnonymous] [Route("ScormContent/{*filePath}")] public async Task ScormContent(string filePath) @@ -79,12 +80,6 @@ public async Task ScormContent(string filePath) try { - var referringUrl = this.Request.Headers["Referer"].ToString(); - if (string.IsNullOrEmpty(referringUrl)) - { - throw new UnauthorizedAccessException("Referer URL is required."); - } - if (!this.User.Identity.IsAuthenticated) { throw new UnauthorizedAccessException("User is not authenticated."); From 5a04e2a44b8660efa3259f85fb25b0447d2700e9 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:29:41 +0100 Subject: [PATCH 08/29] model versin update --- AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj | 2 +- LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj | 2 +- .../LearningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- .../LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- .../LearningHub.Nhs.Migration.Staging.Repository.csproj | 2 +- .../LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index f222dc100..dd474573d 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 6a9ff6ca0..c1930e7fa 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -108,7 +108,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 2c87ba2cb..0af6830e4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 108c74355..55ce79f06 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index ce9034d03..dece11f1c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -24,7 +24,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 191c99713..67c025df9 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -27,7 +27,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index fdacce920..4be0eeb12 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index ca2c85a82..063b86205 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index 2fa729986..22ee37034 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index bca29eeb0..0c1aed810 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 13e00879d..41178c7f4 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 87beb0d40..65fe0dcc3 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index b479d88df..ae225e64c 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 5a7f79921..fdfb3d5d8 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -24,7 +24,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 2306024b3..279defe85 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index c45bd555b..1aae28941 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 2501535f9..217743117 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index ac9a749f6..14710ab84 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index b5fc07022..58db5c448 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 206f7411f8aea5178cfd7f0fc4d39d07056148c0 Mon Sep 17 00:00:00 2001 From: Phil <165780796+Phil-NHS@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:07:59 +0100 Subject: [PATCH 09/29] Merge pull request #522 from TechnologyEnhancedLearning/Develop/Feature/TD-3678-swagger-version-json-update Swagger file was missing certificates endpoint From 94e8a548016484fd2be7e9f056a779acbb9cd6ea Mon Sep 17 00:00:00 2001 From: Phil <165780796+Phil-NHS@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:07:59 +0100 Subject: [PATCH 10/29] Merge pull request #522 from TechnologyEnhancedLearning/Develop/Feature/TD-3678-swagger-version-json-update Swagger file was missing certificates endpoint From 1abcee012db13ba6c684993c2ffc04e79a7ac0a4 Mon Sep 17 00:00:00 2001 From: Phil-NHS Date: Wed, 7 Aug 2024 12:07:41 +0100 Subject: [PATCH 11/29] No task, quick fix, to bookmark endpoint which needs oauth token handling From e7ce5150883a558ddfaac37cb9fd0456f80f5c91 Mon Sep 17 00:00:00 2001 From: Phil <165780796+Phil-NHS@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:07:59 +0100 Subject: [PATCH 12/29] Merge pull request #522 from TechnologyEnhancedLearning/Develop/Feature/TD-3678-swagger-version-json-update Swagger file was missing certificates endpoint From aefa90b8e941530b713b27971d50dd8e52779344 Mon Sep 17 00:00:00 2001 From: Phil <165780796+Phil-NHS@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:07:59 +0100 Subject: [PATCH 13/29] Merge pull request #522 from TechnologyEnhancedLearning/Develop/Feature/TD-3678-swagger-version-json-update Swagger file was missing certificates endpoint From 99c6944eb12979f013bfbec89047c37e337d2361 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:01:29 +0100 Subject: [PATCH 14/29] fixes --- .../Controllers/ResourceController.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index c48aa4f83..ec8774fea 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -120,7 +120,7 @@ public async Task GetResourceRefe /// ids. /// ResourceReferenceViewModels for matching resources. [HttpGet("Bulk")] - public async Task GetResourceReferencesByOriginalIds([FromQuery]List resourceReferenceIds) + public async Task GetResourceReferencesByOriginalIds([FromQuery] List resourceReferenceIds) { if (resourceReferenceIds.Count > MaxNumberOfReferenceIds) { @@ -136,7 +136,7 @@ public async Task GetResourceReferencesByOrigina /// ids. /// ResourceReferenceViewModels for matching resources. [HttpGet("BulkJson")] - public async Task GetResourceReferencesByOriginalIdsFromJson([FromQuery]string resourceReferences) + public async Task GetResourceReferencesByOriginalIdsFromJson([FromQuery] string resourceReferences) { var bulkResourceReferences = JsonConvert.DeserializeObject(resourceReferences); @@ -156,6 +156,7 @@ public async Task GetResourceReferencesByOrigina /// /// Get resourceReferences that have an in progress activity summary /// + /// activityStatusId. /// ResourceReferenceViewModels for matching resources. [HttpGet("User/{activityStatusId}")] public async Task> GetResourceReferencesByActivityStatus(int activityStatusId) @@ -163,9 +164,20 @@ public async Task> GetResour // These activity statuses are set with other activity statuses and resource type within the ActivityStatusHelper.GetActivityStatusDescription // Note In progress is in complete in the db List activityStatusIdsNotInUseInDB = new List() { (int)ActivityStatusEnum.Launched, (int)ActivityStatusEnum.InProgress, (int)ActivityStatusEnum.Viewed, (int)ActivityStatusEnum.Downloaded }; - if (this.CurrentUserId == null) throw new UnauthorizedAccessException("User Id required."); - if (!Enum.IsDefined(typeof(ActivityStatusEnum), activityStatusId)) throw new ArgumentOutOfRangeException($"activityStatusId : {activityStatusId} does not exist within ActivityStatusEnum"); - if (activityStatusIdsNotInUseInDB.Contains(activityStatusId)) throw new ArgumentOutOfRangeException($"activityStatusId: {activityStatusId} does not exist within the database definitions"); + if (this.CurrentUserId == null) + { + throw new UnauthorizedAccessException("User Id required."); + } + + if (!Enum.IsDefined(typeof(ActivityStatusEnum), activityStatusId)) + { + throw new ArgumentOutOfRangeException($"activityStatusId : {activityStatusId} does not exist within ActivityStatusEnum"); + } + + if (activityStatusIdsNotInUseInDB.Contains(activityStatusId)) + { + throw new ArgumentOutOfRangeException($"activityStatusId: {activityStatusId} does not exist within the database definitions"); + } return await this.resourceService.GetResourceReferenceByActivityStatus(new List() { activityStatusId }, this.CurrentUserId.Value); } @@ -177,7 +189,10 @@ public async Task> GetResour [HttpGet("User/Certificates")] public async Task> GetResourceReferencesByCertificates() { - if (this.CurrentUserId == null) throw new UnauthorizedAccessException("User Id required."); + if (this.CurrentUserId == null) + { + throw new UnauthorizedAccessException("User Id required."); + } return await this.resourceService.GetResourceReferencesForCertificates(this.CurrentUserId.Value); } From a7ecd8ab1af7597bfa4e6e12e60e62cea5ba21d8 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Fri, 20 Sep 2024 12:54:33 +0100 Subject: [PATCH 15/29] TD-4682:fix click logs issue -Find-wise --- .../LearningHub.Nhs.AdminUI.csproj | 2 +- .../Controllers/CatalogueController.cs | 2 ++ .../Controllers/SearchController.cs | 8 +++-- .../LearningHub.Nhs.WebUI.csproj | 2 +- .../Views/Catalogue/Catalogues.cshtml | 32 ++++++++++++----- .../Search/_CatalogueSearchResult.cshtml | 34 ++++++++++--------- .../Views/Search/_ResourceSearchResult.cshtml | 24 ++++++------- .../LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- .../LearningHub.Nhs.Services/SearchService.cs | 8 ++--- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 22 files changed, 80 insertions(+), 60 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index dd474573d..4ea6a2e6a 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index efa8cd4f2..477aabfbf 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -110,6 +110,7 @@ public async Task Index(int pageIndex = 1, string term = null) }); catalogues.TotalCount = termCatalogues.TotalHits; + catalogues.GroupId = Guid.NewGuid(); catalogues.Catalogues = termCatalogues.DocumentModel.Select(t => new DashboardCatalogueViewModel { Url = t.Url, @@ -124,6 +125,7 @@ public async Task Index(int pageIndex = 1, string term = null) NodeId = int.Parse(t.Id), BadgeUrl = t.BadgeUrl, Providers = t.Providers, + ClickPayload = t.Click.Payload, }).ToList(); } else diff --git a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs index 2cebfd11c..a87517b97 100644 --- a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs @@ -214,8 +214,9 @@ public async Task RecordCatalogueNavigation(SearchRequestViewMode /// time of search. /// user query. /// search query. + /// the title. [HttpGet("record-resource-click")] - public void RecordResourceClick(string url, int nodePathId, int itemIndex, int pageIndex, int totalNumberOfHits, string searchText, int resourceReferenceId, Guid groupId, string searchId, long timeOfSearch, string userQuery, string query) + public void RecordResourceClick(string url, int nodePathId, int itemIndex, int pageIndex, int totalNumberOfHits, string searchText, int resourceReferenceId, Guid groupId, string searchId, long timeOfSearch, string userQuery, string query, string title) { var searchActionResourceModel = new SearchActionResourceModel { @@ -230,6 +231,7 @@ public void RecordResourceClick(string url, int nodePathId, int itemIndex, int p TimeOfSearch = timeOfSearch, UserQuery = userQuery, Query = query, + Title = title, }; this.searchService.CreateResourceSearchActionAsync(searchActionResourceModel); @@ -251,9 +253,10 @@ public void RecordResourceClick(string url, int nodePathId, int itemIndex, int p /// time of search. /// user query. /// search query. + /// the name. /// A representing the asynchronous operation. [HttpGet("record-catalogue-click")] - public async Task RecordCatalogueClick(string url, int nodePathId, int itemIndex, int pageIndex, int totalNumberOfHits, string searchText, int catalogueId, Guid groupId, string searchId, long timeOfSearch, string userQuery, string query) + public async Task RecordCatalogueClick(string url, int nodePathId, int itemIndex, int pageIndex, int totalNumberOfHits, string searchText, int catalogueId, Guid groupId, string searchId, long timeOfSearch, string userQuery, string query, string name) { SearchActionCatalogueModel searchActionCatalogueModel = new SearchActionCatalogueModel { @@ -268,6 +271,7 @@ public async Task RecordCatalogueClick(string url, int nodePathId TimeOfSearch = timeOfSearch, UserQuery = userQuery, Query = query, + Name = name, }; await this.searchService.CreateCatalogueSearchActionAsync(searchActionCatalogueModel); diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index c1930e7fa..78ab26256 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -108,7 +108,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml index 024f01b95..b976651b2 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml @@ -1,14 +1,30 @@ -@using LearningHub.Nhs.WebUI.Extensions -@using Microsoft.AspNetCore.WebUtilities +@using System.Web; +@using LearningHub.Nhs.WebUI.Extensions; +@using Microsoft.AspNetCore.WebUtilities; +@using LearningHub.Nhs.Models.Search.SearchClick; + @model LearningHub.Nhs.Models.Dashboard.DashboardCatalogueResponseViewModel; @{ - ViewData["Title"] = "Learning Hub - Catalogues"; + ViewData["Title"] = "Learning Hub - Catalogues"; + + var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); + var hasSearchTerm = queryParams.ContainsKey("term"); + var searhTerm = hasSearchTerm ? queryParams["term"].ToString() : null; + string cardStyle = "card-provider-details--blank"; + + string GetCatalogueUrl(string catalogueUrl, SearchClickPayloadModel list, int catalogueId) + { + string encodedCatalogueUrl = HttpUtility.UrlEncode("/Catalogue/" + catalogueUrl); + string searchSignalQueryEncoded = HttpUtility.UrlEncode(HttpUtility.UrlDecode(list?.SearchSignal?.Query)); + string groupId = HttpUtility.UrlEncode(Model.GroupId.ToString()); + var url = $@"/search/record-catalogue-click?url={encodedCatalogueUrl}&itemIndex={list?.HitNumber} +&pageIndex={this.ViewBag.PageIndex}&totalNumberOfHits={list?.SearchSignal?.Stats.TotalHits}&searchText={searhTerm}&catalogueId={catalogueId} +&GroupId={groupId}&searchId={list?.SearchSignal.SearchId}&timeOfSearch={list?.SearchSignal.TimeOfSearch}&userQuery={HttpUtility.UrlEncode(list?.SearchSignal?.UserQuery)} +&query={searchSignalQueryEncoded}&name={list?.DocumentFields?.Name}"; + return url; + } - var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); - var hasSearchTerm = queryParams.ContainsKey("term"); - var searhTerm = hasSearchTerm ? queryParams["term"].ToString() : null; - string cardStyle = "card-provider-details--blank"; } @section styles{ @@ -70,7 +86,7 @@

- @item.Name + @item.Name

diff --git a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml index 56e54eff1..b26b4ba70 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml @@ -1,24 +1,26 @@ @using System.Web; @using LearningHub.Nhs.WebUI.Extensions +@using LearningHub.Nhs.Models.Search.SearchClick; @model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel @{ - var catalogueResult = Model.CatalogueSearchResult; - var pagingModel = Model.CatalogueResultPaging; - var searchString = HttpUtility.UrlEncode(Model.SearchString); - var searchSignal = catalogueResult.Feedback?.FeedbackAction?.Payload?.SearchSignal; - - string GetCatalogueUrl(string catalogueUrl, int nodePathId, int itemIndex, int catalogueId) - { - string encodedCatalogueUrl = HttpUtility.UrlEncode("/Catalogue/" + catalogueUrl); - string groupId = HttpUtility.UrlEncode(Model.GroupId.ToString()); - string searchSignalQueryEncoded = HttpUtility.UrlEncode(HttpUtility.UrlDecode(searchSignal?.Query)); - - var url = $@"/search/record-catalogue-click?url={encodedCatalogueUrl}&nodePathId={nodePathId}&itemIndex={itemIndex} -&pageIndex={pagingModel.CurrentPage}&totalNumberOfHits={catalogueResult.TotalHits}&searchText={searchString}&catalogueId={catalogueId} -&GroupId={groupId}&searchId={searchSignal?.SearchId}&timeOfSearch={searchSignal.TimeOfSearch}&userQuery={HttpUtility.UrlEncode(searchSignal?.UserQuery)}&query={searchSignalQueryEncoded}"; - return url; + var catalogueResult = Model.CatalogueSearchResult; + var pagingModel = Model.CatalogueResultPaging; + var searchString = HttpUtility.UrlEncode(Model.SearchString); + + string GetCatalogueUrl(string catalogueUrl, int nodePathId, int itemIndex, int catalogueId, SearchClickPayloadModel payload) + { + var searchSignal = payload?.SearchSignal; + string encodedCatalogueUrl = HttpUtility.UrlEncode("/Catalogue/" + catalogueUrl); + string groupId = HttpUtility.UrlEncode(Model.GroupId.ToString()); + string searchSignalQueryEncoded = HttpUtility.UrlEncode(HttpUtility.UrlDecode(searchSignal?.Query)); + + var url = $@"/search/record-catalogue-click?url={encodedCatalogueUrl}&nodePathId={nodePathId}&itemIndex={payload?.HitNumber} +&pageIndex={pagingModel.CurrentPage}&totalNumberOfHits={payload?.SearchSignal?.Stats?.TotalHits}&searchText={searchString}&catalogueId={catalogueId} +&GroupId={groupId}&searchId={searchSignal?.SearchId}&timeOfSearch={searchSignal?.TimeOfSearch}&userQuery={HttpUtility.UrlEncode(searchSignal?.UserQuery)} +&query={searchSignalQueryEncoded}&name={payload?.DocumentFields?.Name}"; + return url; } } @@ -71,7 +73,7 @@

- @item.Name + @item.Name

diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml index 415e9d550..d4faffd0d 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml @@ -7,28 +7,24 @@ @using LearningHub.Nhs.Models.Search.SearchFeedback; @using LearningHub.Nhs.Models.Enums; @using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Models.Search.SearchClick; @{ var resourceResult = Model.ResourceSearchResult; var pagingModel = Model.ResourceResultPaging; var index = pagingModel.CurrentPage * pagingModel.PageSize; var searchString = HttpUtility.UrlEncode(Model.SearchString); - var searchSignal = resourceResult.Feedback?.FeedbackAction?.Payload?.SearchSignal; - int qVectorIndex = searchSignal.Query?.IndexOf("q_vector") ?? -1; - var searchSignalQuery = searchSignal?.Query; - // Check if "q_vector" is found in the string. if Yes, Remove "q_vector" and everything after it - if (qVectorIndex != -1) - { - searchSignalQuery = searchSignal?.Query.Substring(0, qVectorIndex); - } - string GetUrl(int resourceReferenceId, int itemIndex, int nodePathId) + + string GetUrl(int resourceReferenceId, int itemIndex, int nodePathId, SearchClickPayloadModel payload) { + var searchSignal = payload?.SearchSignal; string groupId = HttpUtility.UrlEncode(Model.GroupId.ToString()); - string searchSignalQueryEncoded = HttpUtility.UrlEncode(HttpUtility.UrlDecode(searchSignalQuery)); + string searchSignalQueryEncoded = HttpUtility.UrlEncode(HttpUtility.UrlDecode(searchSignal?.Query)); - return $@"/search/record-resource-click?url=/Resource/{resourceReferenceId}&nodePathId={nodePathId}&itemIndex={itemIndex} -&pageIndex={pagingModel.CurrentPage}&totalNumberOfHits={resourceResult.TotalHits}&searchText={searchString}&resourceReferenceId={resourceReferenceId} -&groupId={groupId}&searchId={searchSignal?.SearchId}&timeOfSearch={searchSignal?.TimeOfSearch}&userQuery={HttpUtility.UrlEncode(searchSignal.UserQuery)}&query={searchSignalQueryEncoded}"; + return $@"/search/record-resource-click?url=/Resource/{resourceReferenceId}&nodePathId={nodePathId}&itemIndex={payload?.HitNumber} +&pageIndex={pagingModel.CurrentPage}&totalNumberOfHits={payload?.SearchSignal?.Stats?.TotalHits}&searchText={searchString}&resourceReferenceId={resourceReferenceId} +&groupId={groupId}&searchId={searchSignal?.SearchId}&timeOfSearch={searchSignal?.TimeOfSearch}&userQuery={HttpUtility.UrlEncode(searchSignal.UserQuery)} +&query={searchSignalQueryEncoded}&title={payload?.DocumentFields?.Title}"; } bool showCatalogueFieldsInResources = ViewBag.ShowCatalogueFieldsInResources == null || ViewBag.ShowCatalogueFieldsInResources == true; @@ -41,7 +37,7 @@

- @item.Title + @item.Title

@if (provider != null) diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 67c025df9..82b88c37a 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -27,7 +27,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 4be0eeb12..f38e0260e 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index 063b86205..f61e84c2f 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index 22ee37034..a2ac5f308 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 0c1aed810..0f94d7953 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 41178c7f4..59748eeb8 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 65fe0dcc3..7f312c998 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index ae225e64c..e9fa30eef 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index a247d9aa1..99ea20895 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -11,7 +11,7 @@ namespace LearningHub.Nhs.Services using LearningHub.Nhs.Models.Entities.Analytics; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Models.Search.SearchFeedback; + using LearningHub.Nhs.Models.Search.SearchClick; using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.Services.Helpers; using LearningHub.Nhs.Services.Interface; @@ -512,7 +512,7 @@ public async Task CreateCatalogueSearchTermEvent(Ca /// public async Task SendResourceSearchEventClickAsync(SearchActionResourceModel searchActionResourceModel) { - var searchClickPayloadModel = this.mapper.Map(searchActionResourceModel); + var searchClickPayloadModel = this.mapper.Map(searchActionResourceModel); searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; @@ -532,7 +532,7 @@ public async Task SendResourceSearchEventClickAsync(SearchActionResourceMo /// public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchActionCatalogueModel) { - var searchClickPayloadModel = this.mapper.Map(searchActionCatalogueModel); + var searchClickPayloadModel = this.mapper.Map(searchActionCatalogueModel); searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; @@ -596,7 +596,7 @@ public async Task GetAllCatalogueSearchResultsAsy /// /// The . /// - private async Task SendSearchEventClickAsync(SearchFeedbackPayloadModel searchClickPayloadModel, bool isResource) + private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) { var eventType = isResource ? "resource" : "catalog"; diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index fdfb3d5d8..539697d49 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -24,7 +24,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 279defe85..eb924766b 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 1aae28941..6f4c807b7 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 217743117..df5c3c6c3 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index 14710ab84..16a31717c 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 58db5c448..440ec325e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 68f66fd42adc88e261234908b55af18261ac0890 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Fri, 27 Sep 2024 16:02:33 +0100 Subject: [PATCH 16/29] TD-4373-Allow ppsx file type --- .../LearningHub.Nhs.Database.sqlproj | 3 +- .../Post-Deploy/Script.PostDeployment.sql | 3 +- .../Post-Deploy/Scripts/PPSXFileType.sql | 30 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/PPSXFileType.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 6f39be1b9..48f35761f 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -524,6 +524,7 @@ + @@ -594,4 +595,4 @@ - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index 151c7c2c5..5d99e1884 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -81,4 +81,5 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\InitialiseDataForEmailTemplates.sql :r .\Scripts\TD-2929_ActivityStatusUpdates.sql :r .\Scripts\InitialiseDataForEmailTemplates.sql -:r .\Scripts\AttributeData.sql \ No newline at end of file +:r .\Scripts\AttributeData.sql +:r .\Scripts\PPSXFileType.sql \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/PPSXFileType.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/PPSXFileType.sql new file mode 100644 index 000000000..d948664ad --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/PPSXFileType.sql @@ -0,0 +1,30 @@ +IF NOT EXISTS(SELECT Id FROM [resources].[FileType] where Extension ='ppsx') +BEGIN +INSERT INTO [resources].[FileType] + (Id, + [DefaultResourceTypeId] + ,[Name] + ,[Description] + ,[Extension] + ,[Icon] + ,[NotAllowed] + ,[Deleted] + ,[CreateUserId] + ,[CreateDate] + ,[AmendUserId] + ,[AmendDate]) + VALUES + (70, + 9 + ,'PowerPoint Open XML Slide Show' + ,'PowerPoint Open XML Slide Show' + ,'ppsx' + ,'a-mppoint-icon.svg' + ,0 + ,0 + ,57541 + ,SYSDATETIMEOFFSET() + ,57541 + ,SYSDATETIMEOFFSET()) +END +GO \ No newline at end of file From 315b4e9aa7ba5d54cb5401b783bbbd0c89aa0a79 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Tue, 1 Oct 2024 10:52:12 +0100 Subject: [PATCH 17/29] TD-4430: Duplicate keywords issue --- AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml | 6 +++--- .../contribute-resource/components/KeyWordsEditor.vue | 4 +++- .../Scripts/vuesrc/contribute/ContentCommon.vue | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml index 629e03c7c..5d69e9ee3 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml @@ -360,14 +360,14 @@ // Split the input value by commas and trim each keyword var values = value.split(',').map(function (item) { - return item.trim(); + return item.trim().toLowerCase(); }); var duplicateKeywords = []; $('#keyword-error-span').hide(); values.forEach(function (value) { if (value && keywords.indexOf(value) === -1) { - keywords.push(value); + keywords.push(value); $('#Keywords').val(keywords); var tag = $('

' + value + '

'); tag.find('.fa-times').on('click', removeKeyword); @@ -386,7 +386,7 @@ } }); - $keywordInput.val(""); + $keywordInput.val(""); if (keywords.length > 4) { $('#add-keyword').attr('disabled', 'disabled'); $('#add-keyword-input').attr('disabled', 'disabled'); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue index 38c9c50c7..97e4ed42f 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/KeyWordsEditor.vue @@ -82,10 +82,11 @@ }, async addKeyword() { if (this.newKeyword && this.newKeywordTrimmed.length > 0) { + this.keywordChange(); let allTrimmedKeyword = this.newKeywordTrimmed.toLowerCase().split(','); allTrimmedKeyword = allTrimmedKeyword.filter(e => String(e).trim()); for (var i = 0; i < allTrimmedKeyword.length; i++) { - let item = allTrimmedKeyword[i]; + let item = allTrimmedKeyword[i].trim(); if (item.length > 0 && item.length <= 50) { let newKeywordObj = new KeywordModel({ keyword: item, @@ -96,6 +97,7 @@ this.resourceDetails.resourceKeywords.push(newKeywordObj); this.newKeyword = ''; } else if (newKeywordObj.id == 0) { + this.newKeyword = ''; this.keywordError = true; this.keywordErrorMessage.push(item); } diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue index 2f1f07809..8304843e1 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentCommon.vue @@ -537,10 +537,11 @@ }, async addKeyword() { if (this.newKeyword && this.newKeywordTrimmed.length > 0) { + this.keywordChange(); let allTrimmedKeyword = this.newKeywordTrimmed.toLowerCase().split(','); allTrimmedKeyword = allTrimmedKeyword.filter(e => String(e).trim()); for (var i = 0; i < allTrimmedKeyword.length; i++) { - let item = allTrimmedKeyword[i]; + let item = allTrimmedKeyword[i].trim(); if (item.length > 0 && item.length <= 50) { let newkeywordObj = new KeywordModel(); newkeywordObj.keyword = item; @@ -554,6 +555,7 @@ } this.newKeyword = ''; } else if (newkeywordObj.id == 0) { + this.newKeyword = ''; this.keywordError = true; this.keywordErrorMessage.push(item); } From 8888bc500d8d97379e2258513d49adc04a97d3db Mon Sep 17 00:00:00 2001 From: ArunimaGeorge <163844873+ArunimaGeorge@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:01:30 +0100 Subject: [PATCH 18/29] Update Edit.cshtml --- AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml index 5d69e9ee3..19ed8d399 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml @@ -367,7 +367,7 @@ $('#keyword-error-span').hide(); values.forEach(function (value) { if (value && keywords.indexOf(value) === -1) { - keywords.push(value); + keywords.push(value); $('#Keywords').val(keywords); var tag = $('

' + value + '

'); tag.find('.fa-times').on('click', removeKeyword); @@ -457,4 +457,4 @@ -} \ No newline at end of file +} From 0437380f1c113a8adf0bd3ab786487b707d6de8c Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 2 Oct 2024 15:15:43 +0100 Subject: [PATCH 19/29] TD-4388: Assessment resource settings need a required field marker --- .../ContributeAssessmentSettings.vue | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue index ab3a50bd0..b828110a1 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/ContributeAssessmentSettings.vue @@ -65,7 +65,7 @@
-
Provide guidance for the learner at the end of this assessment.
+
Provide guidance for the learner at the end of this assessment.
Tip You can offer guidance to the learner at the end of the assessment such as next steps or recommendations on other learning resources to try.
- +
@@ -131,6 +131,7 @@ endGuidance: "", initialGuidance: "", guidanceValid: true, + IsVisible: false, } }, watch: { @@ -139,7 +140,7 @@ { this.assessmentDetails.endGuidance.addBlock(BlockTypeEnum.Text); } - this.assessmentDetails.endGuidance.blocks[0].textBlock.content = this.endGuidance; + this.assessmentDetails.endGuidance.blocks[0].textBlock.content = this.endGuidance; }, ["assessmentDetails.passMark"](value){ this.assessmentDetails.passMark = this.capNumberFieldBy(value, 100)}, ["assessmentDetails.maximumAttempts"](value){ this.assessmentDetails.maximumAttempts = this.capNumberFieldBy(value, 10)}, @@ -156,6 +157,14 @@ } this.assessmentDetails.assessmentSettingsAreValid = settingsAreValid; + + if (this.endGuidance != "") { + this.IsVisible = true; + } + else { + this.IsVisible = false; + } + return settingsAreValid; }, }, @@ -169,9 +178,23 @@ { this.endGuidance = description; } + + if (this.endGuidance != "") { + this.IsVisible = true; + } + else { + this.IsVisible = false; + } }, setGuidanceValidity(valid: boolean) { - this.guidanceValid = valid; + if (this.endGuidance == "") { + this.guidanceValid = false; + this.IsVisible = false; + } + else { + this.guidanceValid = valid; + this.IsVisible = true; + } } } }); From 9d4bc518cf5b3ef44854c839d8d2f5384e9a244b Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 3 Oct 2024 11:41:13 +0100 Subject: [PATCH 20/29] TD-4819: LH-Issue showing 'Certificates' section blank on 'My accessed learning' tray --- .../LearningHub.Nhs.Database.sqlproj | 1 + ...LearningCertificatesDashboardResources.sql | 101 ++++++++++++++++++ .../Resources/ResourceVersionRepository.cs | 2 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyLearningCertificatesDashboardResources.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 48f35761f..477a48869 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -525,6 +525,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyLearningCertificatesDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyLearningCertificatesDashboardResources.sql new file mode 100644 index 000000000..986e25423 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyLearningCertificatesDashboardResources.sql @@ -0,0 +1,101 @@ +------------------------------------------------------------------------------- +-- Author OA +-- Created 24 JUN 2024 Nov 2020 +-- Purpose Break down the GetDashboardResources SP to smaller SP for a specific data type +-- +-- Modification History +-- +-- 24 Jun 2024 OA Initial Revision +------------------------------------------------------------------------------- + +CREATE PROCEDURE [resources].[GetMyLearningCertificatesDashboardResources] + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +AS +BEGIN + DECLARE @MaxPageNumber INT = 4 + + IF @PageNumber > 4 + BEGIN + SET @PageNumber = @MaxPageNumber + END + + DECLARE @FetchRows INT = 3 + DECLARE @MaxRows INT = @MaxPageNUmber * @FetchRows + DECLARE @OffsetRows INT = (@PageNumber - 1) * @FetchRows + + DECLARE @MyActivity TABLE (ResourceId [int] NOT NULL PRIMARY KEY, ResourceActivityId [int] NOT NULL); + DECLARE @Resources TABLE (ResourceId [int] NOT NULL PRIMARY KEY, ResourceActivityCount [int] NOT NULL); + + INSERT INTO @MyActivity + SELECT TOP (@MaxRows) ra.ResourceId, MAX(ra.Id) ResourceActivityId + FROM + activity.ResourceActivity ra + JOIN [resources].[Resource] r ON ra.ResourceId = r.Id + JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId + LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId + LEFT JOIN [activity].[AssessmentResourceActivity] ara ON ara.ResourceActivityId = ra.Id + LEFT JOIN [activity].[MediaResourceActivity] mar ON mar.ResourceActivityId = ra.Id + LEFT JOIN [activity].[ScormActivity] sa ON sa.ResourceActivityId = ra.Id + WHERE ra.UserId = @UserId AND rv.CertificateEnabled = 1 + AND ( + (r.ResourceTypeId IN (2, 7) AND ra.ActivityStatusId = 3 OR ra.ActivityStart < '2020-09-07 00:00:00 +00:00' OR mar.Id IS NOT NULL AND mar.PercentComplete = 100) + OR (r.ResourceTypeId = 6 AND (sa.CmiCoreLesson_status IN(3,5) OR (ra.ActivityStatusId IN(3, 5)))) + OR ((r.ResourceTypeId = 11 AND arv.AssessmentType = 2) AND (ara.Score >= arv.PassMark OR ra.ActivityStatusId IN(3, 5))) + OR ((r.ResourceTypeId = 11 AND arv.AssessmentType =1) AND (ara.Score >= arv.PassMark AND ra.ActivityStatusId IN(3, 5,7))) + OR (r.ResourceTypeId IN (1, 5, 8, 9, 10, 12) AND ra.ActivityStatusId = 3)) + GROUP BY ra.ResourceId + ORDER BY ResourceActivityId DESC + + SELECT r.Id AS ResourceId + ,( SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0 + ) AS ResourceReferenceID + ,r.CurrentResourceVersionId AS ResourceVersionId + ,r.ResourceTypeId AS ResourceTypeId + ,rv.Title + ,rv.Description + ,CASE + WHEN r.ResourceTypeId = 7 THEN + (SELECT vrv.DurationInMilliseconds from [resources].[VideoResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) + WHEN r.ResourceTypeId = 2 THEN + (SELECT vrv.DurationInMilliseconds from [resources].[AudioResourceVersion] vrv WHERE vrv.[ResourceVersionId] = r.CurrentResourceVersionId) + ELSE + NULL + END AS DurationInMilliseconds + ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.Name END AS CatalogueName + ,cnv.Url AS Url + ,CASE WHEN n.id = 1 THEN NULL ELSE cnv.BadgeUrl END AS BadgeUrl + ,cnv.RestrictedAccess + ,CAST(CASE WHEN cnv.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 ELSE 1 END AS bit) AS HasAccess + ,ub.Id AS BookMarkId + ,CAST(ISNULL(ub.[Deleted], 1) ^ 1 AS BIT) AS IsBookmarked + ,rvrs.AverageRating + ,rvrs.RatingCount +FROM @MyActivity ma +JOIN activity.ResourceActivity ra ON ra.id = ma.ResourceActivityId +JOIN resources.resourceversion rv ON rv.id = ra.ResourceVersionId AND rv.Deleted = 0 +JOIN Resources.Resource r ON r.Id = rv.ResourceId +JOIN hierarchy.Publication p ON rv.PublicationId = p.Id AND p.Deleted = 0 +JOIN resources.ResourceVersionRatingSummary rvrs ON rv.Id = rvrs.ResourceVersionId AND rvrs.Deleted = 0 +JOIN hierarchy.NodeResource nr ON r.Id = nr.ResourceId AND nr.Deleted = 0 +JOIN hierarchy.Node n ON n.Id = nr.NodeId AND n.Hidden = 0 AND n.Deleted = 0 +JOIN hierarchy.NodePath np ON np.NodeId = n.Id AND np.Deleted = 0 AND np.IsActive = 1 +JOIN hierarchy.NodeVersion nv ON nv.NodeId = np.CatalogueNodeId AND nv.VersionStatusId = 2 AND nv.Deleted = 0 +JOIN hierarchy.CatalogueNodeVersion cnv ON cnv.NodeVersionId = nv.Id AND cnv.Deleted = 0 +LEFT JOIN hub.UserBookmark ub ON ub.UserId = @UserId AND ub.ResourceReferenceId = (SELECT TOP 1 rr.OriginalResourceReferenceId + FROM [resources].[ResourceReference] rr + JOIN hierarchy.NodePath np on np.id = rr.NodePathId and np.NodeId = n.Id and np.Deleted = 0 + WHERE rr.ResourceId = rv.ResourceId AND rr.Deleted = 0) +LEFT JOIN ( SELECT DISTINCT CatalogueNodeId + FROM [hub].[RoleUserGroupView] rug JOIN hub.UserUserGroup uug ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 and rug.RoleId in (1,2,3) and uug.Deleted = 0 and uug.UserId = @userId) auth ON n.Id = auth.CatalogueNodeId +ORDER BY ma.ResourceActivityId DESC, rv.Title +OFFSET @OffsetRows ROWS +FETCH NEXT @FetchRows ROWS ONLY + + SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END FROM @MyActivity +END \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Repository/Resources/ResourceVersionRepository.cs b/WebAPI/LearningHub.Nhs.Repository/Resources/ResourceVersionRepository.cs index 584da6344..cee3e6a07 100644 --- a/WebAPI/LearningHub.Nhs.Repository/Resources/ResourceVersionRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository/Resources/ResourceVersionRepository.cs @@ -690,7 +690,7 @@ public List GetContributions(int userId, ResourceContri switch (dashboardType) { case "my-certificates": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyCertificatesDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyLearningCertificatesDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "my-recent-completed": dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); From ff2d0e2043a75da14a5095efd8913daa90993738 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:07:11 +0100 Subject: [PATCH 21/29] removed -hyperlink --- OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index 4fdbbba8b..dcfd80c44 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -3,7 +3,7 @@ "info": { "title": "LearningHub.NHS.OpenAPI", "version": "1.3.0", - "description": "A set of API endpoints for retrieving learning resource information from the Learning Hub learning platform. The [Learning Hub](https://learninghub.nhs.uk/) is a platform for hosting and sharing learning resources for health and social care provided by Technology Enhanced Learning (TEL) at NHS England. An application API key must be used to authorise calls to the API from external applications. To contact TEL to discuss connecting your external system to the Learning Hub, email [england.tel@nhs.net](england.tel@nhs.net)" + "description": "A set of API endpoints for retrieving learning resource information from the Learning Hub learning platform. The [Learning Hub](https://learninghub.nhs.uk/) is a platform for hosting and sharing learning resources for health and social care provided by Technology Enhanced Learning (TEL) at NHS England. An application API key must be used to authorise calls to the API from external applications. To contact TEL to discuss connecting your external system to the Learning Hub, email england.tel@nhs.net." }, "paths": { "/Bookmark/GetAllByParent": { From 702fc223fee3a6f36ae007ede2f9cca65af123f9 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:56:56 +0100 Subject: [PATCH 22/29] defectfix --- .../Views/Shared/_FooterPartial.cshtml | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_FooterPartial.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_FooterPartial.cshtml index 29446946a..2c6e87651 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_FooterPartial.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_FooterPartial.cshtml @@ -3,28 +3,27 @@ @using LearningHub.Nhs.WebUI.Configuration; @inject IOptions settings; - +
-@functions{ +@functions { public bool SystemOffline() { return ViewContext.RouteData.Values["controller"].ToString() == "Offline"; From dc01d8cba9edd2d804ae2ca38d0a8b1cbb57c167 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 16 Oct 2024 15:07:58 +0100 Subject: [PATCH 23/29] Fixed the issue with the My learning tray. Also included the excluded sp's and tables in the DB sloution. --- .../LearningHub.Nhs.Database.sqlproj | 9 +++++++++ .../Resources/GetMyInProgressDashboardResources.sql | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 477a48869..b00ee6b67 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -526,6 +526,15 @@ + + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql index 7464de6e3..2221b68ef 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql @@ -31,7 +31,7 @@ BEGIN INSERT INTO @MyActivity SELECT TOP (@MaxRows) ra.ResourceId, MAX(ra.Id) ResourceActivityId FROM - (SELECT a.Id,a.ResourceId,a.ResourceVersionId,a.LaunchResourceActivityId,a.UserId,a.ActivityStatusId,a.ActivityStart FROM activity.ResourceActivity a INNER JOIN (SELECT ResourceId, MAX(Id) as id FROM activity.ResourceActivity GROUP BY ResourceId) AS b ON a.ResourceId = b.ResourceId AND a.id = b.id order by a.Id desc OFFSET 0 ROWS) ra + (SELECT a.Id,a.ResourceId,a.ResourceVersionId,a.LaunchResourceActivityId,a.UserId,a.ActivityStatusId,a.ActivityStart FROM activity.ResourceActivity a LEFT JOIN (SELECT ResourceId, MAX(Id) as id FROM activity.ResourceActivity GROUP BY ResourceId) AS b ON a.ResourceId = b.ResourceId AND a.id = b.id order by a.Id desc OFFSET 0 ROWS) ra JOIN [resources].[Resource] r ON ra.ResourceId = r.Id JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId From 8e332142207276dc5a469f161cb3f1aae5607f61 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 16 Oct 2024 16:22:26 +0100 Subject: [PATCH 24/29] excluded unwanted script --- .../Scripts/ELFH Database Scripts/Set up LH Tenant.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/ELFH Database Scripts/Set up LH Tenant.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/ELFH Database Scripts/Set up LH Tenant.sql index 3fae0f800..36cc3ff9d 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/ELFH Database Scripts/Set up LH Tenant.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/ELFH Database Scripts/Set up LH Tenant.sql @@ -1,4 +1,5 @@ -/* + +/* Script for adding the Learning Hub tenant */ --Required Information - UPDATE BEFORE RUNNING From f75df9130457d5a5fc101210208ffff00eeb49bc Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:37:05 +0100 Subject: [PATCH 25/29] Update LearningHub.Nhs.Database.sqlproj --- .../LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index b00ee6b67..f7552e62f 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -508,7 +508,6 @@ - @@ -605,4 +604,4 @@ - \ No newline at end of file + From 90515665bf75950aec8da534b8bae490ecae389f Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 17 Oct 2024 10:05:56 +0100 Subject: [PATCH 26/29] Deleted unwanted files --- .../LearningHub.Nhs.Database.sqlproj | 3 +-- .../AddDigitalLearningSolutionsSso.sql | 23 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/External/AddDigitalLearningSolutionsSso.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index f7552e62f..8c11263af 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -526,7 +526,6 @@ - @@ -604,4 +603,4 @@ - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/External/AddDigitalLearningSolutionsSso.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/External/AddDigitalLearningSolutionsSso.sql deleted file mode 100644 index 27c0d2056..000000000 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/External/AddDigitalLearningSolutionsSso.sql +++ /dev/null @@ -1,23 +0,0 @@ -/* -Script for adding DigitalLearningSolutionsSso ExternalSystemUser to users which already have DigitalLearningSolutions ExternalSystemUser -*/ -DECLARE @DigitalLearningSolutionsId INT; -DECLARE @DigitalLearningSolutionsSsoId INT; - -SELECT @DigitalLearningSolutionsId = id - FROM [external].[ExternalSystem] - WHERE code = 'DigitalLearningSolutions'; - -SELECT @DigitalLearningSolutionsSsoId = id - FROM [external].[ExternalSystem] - WHERE code = 'DigitalLearningSolutionsSso'; - -INSERT INTO [external].[ExternalSystemUser] (UserId, ExternalSystemId, Deleted, CreateUserId, CreateDate, AmendUserId, AmendDate) -SELECT UserId, @DigitalLearningSolutionsId, Deleted, 4, SYSDATETIMEOFFSET(), 4, SYSDATETIMEOFFSET() -FROM [external].[ExternalSystemUser] -WHERE ExternalSystemId = @DigitalLearningSolutionsSsoId - AND userId NOT IN ( - SELECT userId - FROM [external].[ExternalSystemUser] - WHERE ExternalSystemId = @DigitalLearningSolutionsId - ); \ No newline at end of file From 1d4eef473ccf54f9ac61980eb48de3d59e3ae136 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Fri, 18 Oct 2024 11:46:50 +0100 Subject: [PATCH 27/29] To fix the issue with the statuses --- .../Services/SearchService.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index c5ede76fb..c5ad21caf 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -94,7 +94,15 @@ private async Task> GetResourceMetadataViewModel var resourcesFound = await this.resourceRepository.GetResourcesFromIds(findwiseResourceIds); - List resourceMetadataViewModels = resourcesFound.Select(resource => MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) + if (currentUserId.HasValue) + { + List resourceIds = resourcesFound.Select(x => x.Id).ToList(); + List userIds = new List() { currentUserId.Value }; + + resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; + } + + List resourceMetadataViewModels = resourcesFound.Select(resource => this.MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) .OrderBySequence(findwiseResourceIds) .ToList(); @@ -109,13 +117,6 @@ private async Task> GetResourceMetadataViewModel unmatchedResourcesIdsString); } - if (currentUserId.HasValue) - { - List resourceIds = resourcesFound.Select(x => x.Id).ToList(); - List userIds = new List() { currentUserId.Value }; - - resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; - } return resourceMetadataViewModels; } From 9bb0d7cebba121eb282ea56c111f6202161c83ac Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 21 Oct 2024 11:11:45 +0100 Subject: [PATCH 28/29] TD-2895: Modify Learning Resource endpoints to retrieve user status for learning resource (SIT fixes) --- AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj | 2 +- LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs | 3 ++- WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj | 2 +- .../LearningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- .../LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- .../LearningHub.Nhs.Migration.Staging.Repository.csproj | 2 +- .../LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj | 2 +- 20 files changed, 21 insertions(+), 20 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index 4ea6a2e6a..798171c31 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 78ab26256..3f3f09de7 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -108,7 +108,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 0af6830e4..6062a7b7a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 55ce79f06..3d46e57aa 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index dece11f1c..26457744e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -24,7 +24,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index c5ad21caf..2614ed8b7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -83,6 +83,7 @@ private async Task> GetResourceMetadataViewModel FindwiseResultModel findwiseResultModel, int? currentUserId) { List resourceActivities = new List() { }; + List resourceMetadataViewModels = new List() { }; var documentsFound = findwiseResultModel.SearchResults?.DocumentList.Documents?.ToList() ?? new List(); var findwiseResourceIds = documentsFound.Select(d => int.Parse(d.Id)).ToList(); @@ -102,7 +103,7 @@ private async Task> GetResourceMetadataViewModel resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; } - List resourceMetadataViewModels = resourcesFound.Select(resource => this.MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) + resourceMetadataViewModels = resourcesFound.Select(resource => this.MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) .OrderBySequence(findwiseResourceIds) .ToList(); diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 82b88c37a..b1b3f6e95 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -27,7 +27,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index f38e0260e..4a089b9a3 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index f61e84c2f..02c58af7b 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index a2ac5f308..a6c146998 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 0f94d7953..3173c4895 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 59748eeb8..a7a9f0048 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 7f312c998..d64166759 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index e9fa30eef..c8b4ec333 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 539697d49..f59f836ec 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -24,7 +24,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index eb924766b..306bfba43 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 6f4c807b7..fe0eec05d 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index df5c3c6c3..5a85cff3e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index 16a31717c..c167e11e4 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 440ec325e..6c31da645 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From daa0c3dd141d1ffd485a39433f54569e9a1c153e Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 21 Oct 2024 14:59:39 +0100 Subject: [PATCH 29/29] Fixed the my learning tray issue --- .../Resources/GetMyInProgressDashboardResources.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql index 2221b68ef..7464de6e3 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyInProgressDashboardResources.sql @@ -31,7 +31,7 @@ BEGIN INSERT INTO @MyActivity SELECT TOP (@MaxRows) ra.ResourceId, MAX(ra.Id) ResourceActivityId FROM - (SELECT a.Id,a.ResourceId,a.ResourceVersionId,a.LaunchResourceActivityId,a.UserId,a.ActivityStatusId,a.ActivityStart FROM activity.ResourceActivity a LEFT JOIN (SELECT ResourceId, MAX(Id) as id FROM activity.ResourceActivity GROUP BY ResourceId) AS b ON a.ResourceId = b.ResourceId AND a.id = b.id order by a.Id desc OFFSET 0 ROWS) ra + (SELECT a.Id,a.ResourceId,a.ResourceVersionId,a.LaunchResourceActivityId,a.UserId,a.ActivityStatusId,a.ActivityStart FROM activity.ResourceActivity a INNER JOIN (SELECT ResourceId, MAX(Id) as id FROM activity.ResourceActivity GROUP BY ResourceId) AS b ON a.ResourceId = b.ResourceId AND a.id = b.id order by a.Id desc OFFSET 0 ROWS) ra JOIN [resources].[Resource] r ON ra.ResourceId = r.Id JOIN [resources].[ResourceVersion] rv ON rv.Id = ra.ResourceVersionId LEFT JOIN [resources].[AssessmentResourceVersion] arv ON arv.ResourceVersionId = ra.ResourceVersionId