From c79d8d60f49060e47848917ca94dcd34dd68d267 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:07:42 +0100 Subject: [PATCH 01/36] Merge pull request #532 from TechnologyEnhancedLearning/Develop/Feature/TD-4037-AllCatalogue View all catalogues changes --- .../Configuration/FindwiseSettings.cs | 5 + .../Configuration/Settings.cs | 5 + .../Controllers/CatalogueController.cs | 58 ++++++++ .../Interfaces/ICatalogueService.cs | 8 + .../Interfaces/ISearchService.cs | 7 + .../Models/NavigationModel.cs | 5 + .../Services/CatalogueService.cs | 28 ++++ .../Services/NavigationPermissionService.cs | 7 + .../Services/SearchService.cs | 47 ++++++ .../Styles/nhsuk/pages/catalogue.scss | 13 +- .../Views/Catalogue/AllCatalogue.cshtml | 137 ++++++++++++++++++ .../Views/Catalogue/AllCatalogueSearch.cshtml | 114 +++++++++++++++ .../Catalogue/_AllCataloguePagination.cshtml | 36 +++++ .../Catalogue/_AllCatalogueSearchBar.cshtml | 13 ++ .../Views/Home/_CatalogueTray.cshtml | 6 +- .../Components/NavigationItems/Default.cshtml | 9 ++ LearningHub.Nhs.WebUI/appsettings.json | 6 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 1 + ...earningHub.Nhs.OpenApi.Repositories.csproj | 1 + .../LearningHub.Nhs.OpenApi.Tests.csproj | 1 + .../LearningHub.NHS.OpenAPI.csproj | 1 + .../Controllers/CatalogueController.cs | 14 ++ .../Controllers/SearchController.cs | 85 +++++++++++ .../Hierarchy/GetCatalogues.sql | 39 +++++ .../Hierarchy/GetCataloguesCount.sql | 61 ++++++++ .../ICatalogueNodeVersionRepository.cs | 16 ++ .../CatalogueNodeVersionRepository.cs | 33 +++++ .../LearningHubDbContext.cs | 10 ++ .../ICatalogueService.cs | 9 ++ .../ISearchService.cs | 7 + .../CatalogueService.cs | 61 +++++++- .../LearningHub.Nhs.Services/SearchService.cs | 47 ++++++ 32 files changed, 884 insertions(+), 6 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/Catalogue/_AllCataloguePagination.cshtml create mode 100644 LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCatalogues.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCataloguesCount.sql diff --git a/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs b/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs index dcac6798d..683d5b4c9 100644 --- a/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs @@ -14,5 +14,10 @@ public class FindwiseSettings /// Gets or sets the CatalogueSearchPageSize. /// public int CatalogueSearchPageSize { get; set; } + + /// + /// Gets or sets the AllCatalogueSearchPageSize. + /// + public int AllCatalogueSearchPageSize { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index d107ac2d5..9764d293e 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -245,5 +245,10 @@ public Settings() /// Gets or sets the MediaKindSettings. /// public MediaKindSettings MediaKindSettings { get; set; } = new MediaKindSettings(); + + /// + /// Gets or sets AllCataloguePageSize. + /// + public int AllCataloguePageSize { get; set; } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index fb4d3def3..52bb1125d 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -551,5 +551,63 @@ public async Task RequestPreviewAccess(CatalogueRequestAccessView return this.View("RequestPreviewAccess", viewModel); } } + + /// + /// Get all catelogues, filter and pagination based on alphabets. + /// + /// filterChar. + /// rk. + [Route("/allcatalogue")] + [Route("/allcatalogue/{filterChar}")] + public async Task GetAllCatalogue(string filterChar = "a") + { + var pageSize = this.settings.AllCataloguePageSize; + var catalogues = await this.catalogueService.GetAllCatalogueAsync(filterChar, pageSize); + return this.View("allcatalogue", catalogues); + } + + /// + /// AllCatalogues Search. + /// + /// pageIndex. + /// Search term. + /// IActionResult. + [Route("/allcataloguesearch")] + public async Task GetAllCatalogueSearch(int pageIndex = 1, string term = null) + { + var catalogues = new AllCatalogueSearchResponseViewModel(); + var searchString = term?.Trim() ?? string.Empty; + var allCatalogueSearchPageSize = this.settings.FindwiseSettings.AllCatalogueSearchPageSize; + + if (!string.IsNullOrWhiteSpace(term)) + { + var termCatalogues = await this.searchService.GetAllCatalogueSearchResultAsync( + new AllCatalogueSearchRequestModel + { + SearchText = searchString, + PageIndex = pageIndex - 1, + PageSize = allCatalogueSearchPageSize, + }); + + catalogues.TotalCount = termCatalogues.TotalHits; + catalogues.Catalogues = termCatalogues.DocumentModel.Select(t => new AllCatalogueViewModel + { + Url = t.Url, + Name = t.Name, + CardImageUrl = t.CardImageUrl, + BannerUrl = t.BannerUrl, + Description = t.Description, + RestrictedAccess = t.RestrictedAccess, + HasAccess = t.HasAccess, + IsBookmarked = t.IsBookmarked, + BookmarkId = t.BookmarkId, + NodeId = int.Parse(t.Id), + BadgeUrl = t.BadgeUrl, + }).ToList(); + } + + this.ViewBag.PageIndex = pageIndex; + return this.View("AllCatalogueSearch", catalogues); + } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs index 30ba46a54..635eee151 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs @@ -138,5 +138,13 @@ public interface ICatalogueService /// The user - user group id. /// The validation result. Task RemoveUserFromRestrictedAccessUserGroup(int userUserGroupId); + + /// + /// The GetAllCatalogueAsync. + /// + /// The letter. + /// The pageSize. + /// The allcatalogue result based on letters. + Task GetAllCatalogueAsync(string filterChar, int pageSize); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs b/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs index 4f8ed36c7..b01be5bce 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs @@ -77,5 +77,12 @@ public interface ISearchService /// catalogue search request model. /// The . Task CreateCatalogueSearchTermEventAsync(CatalogueSearchRequestModel catalogueSearchRequestModel); + + /// + /// Get AllCatalogue Search Result Async. + /// + /// The catalogue Search Request Model. + /// The . + Task GetAllCatalogueSearchResultAsync(AllCatalogueSearchRequestModel catalogueSearchRequestModel); } } diff --git a/LearningHub.Nhs.WebUI/Models/NavigationModel.cs b/LearningHub.Nhs.WebUI/Models/NavigationModel.cs index ca4dee523..950343bde 100644 --- a/LearningHub.Nhs.WebUI/Models/NavigationModel.cs +++ b/LearningHub.Nhs.WebUI/Models/NavigationModel.cs @@ -69,5 +69,10 @@ public class NavigationModel /// Gets or sets a value indicating whether to show my account. /// public bool ShowMyAccount { get; set; } + + /// + /// Gets or sets a value indicating whether to show Browse Catalogues. + /// + public bool ShowBrowseCatalogues { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs index fc95fe746..615363cfd 100644 --- a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs @@ -602,5 +602,33 @@ public async Task RemoveUserFromRestrictedAccessUse return apiResponse.ValidationResult; } + + /// + /// GetAllCatalogueAsync. + /// + /// The filterChar. + /// the pageSize. + /// A representing the result of the asynchronous operation. + public async Task GetAllCatalogueAsync(string filterChar, int pageSize) + { + AllCatalogueResponseViewModel viewmodel = new AllCatalogueResponseViewModel { }; + var client = await this.LearningHubHttpClient.GetClientAsync(); + + var request = $"catalogue/allcatalogues/{pageSize}/{filterChar}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } } } diff --git a/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs b/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs index 954b20ca7..95e74022e 100644 --- a/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs +++ b/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs @@ -80,6 +80,7 @@ public NavigationModel NotAuthenticated() ShowRegister = false, ShowSignOut = false, ShowMyAccount = false, + ShowBrowseCatalogues = false, }; } @@ -104,6 +105,7 @@ private NavigationModel AuthenticatedAdministrator(string controllerName) ShowRegister = false, ShowSignOut = true, ShowMyAccount = true, + ShowBrowseCatalogues = true, }; } @@ -128,6 +130,7 @@ private NavigationModel AuthenticatedBlueUser(string controllerName) ShowRegister = false, ShowSignOut = true, ShowMyAccount = true, + ShowBrowseCatalogues = true, }; } @@ -151,6 +154,7 @@ private NavigationModel AuthenticatedGuest() ShowRegister = false, ShowSignOut = true, ShowMyAccount = false, + ShowBrowseCatalogues = false, }; } @@ -175,6 +179,7 @@ private async Task AuthenticatedReadOnly(string controllerName) ShowRegister = false, ShowSignOut = true, ShowMyAccount = false, + ShowBrowseCatalogues = true, }; } @@ -198,6 +203,7 @@ private async Task AuthenticatedBasicUserOnly() ShowRegister = false, ShowSignOut = true, ShowMyAccount = true, + ShowBrowseCatalogues = true, }; } @@ -221,6 +227,7 @@ private NavigationModel InLoginWizard() ShowRegister = false, ShowSignOut = true, ShowMyAccount = false, + ShowBrowseCatalogues = false, }; } } diff --git a/LearningHub.Nhs.WebUI/Services/SearchService.cs b/LearningHub.Nhs.WebUI/Services/SearchService.cs index 3fc38d8a2..8e22e0eeb 100644 --- a/LearningHub.Nhs.WebUI/Services/SearchService.cs +++ b/LearningHub.Nhs.WebUI/Services/SearchService.cs @@ -587,6 +587,53 @@ public async Task CreateCatalogueSearchTermEventAsync(CatalogueSearchReques } } + /// + /// GetAllCatalogueSearchResultAsync. + /// + /// catalogueSearchRequestModel. + /// The . + public async Task GetAllCatalogueSearchResultAsync(AllCatalogueSearchRequestModel catalogueSearchRequestModel) + { + SearchAllCatalogueViewModel searchViewModel = new SearchAllCatalogueViewModel(); + + try + { + var client = await this.LearningHubHttpClient.GetClientAsync(); + + catalogueSearchRequestModel.SearchText = this.DecodeProblemCharacters(catalogueSearchRequestModel.SearchText); + + var json = JsonConvert.SerializeObject(catalogueSearchRequestModel); + var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); + + var request = $"Search/GetAllCatalogueSearchResult"; + var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + searchViewModel = JsonConvert.DeserializeObject(result); + + if (searchViewModel.DocumentModel != null + && searchViewModel.DocumentModel.Count != 0) + { + searchViewModel.DocumentModel.ForEach(x => x.Description = this.RemoveHtmlTags(x.Description)); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return searchViewModel; + } + catch (Exception ex) + { + searchViewModel.ErrorOnAPI = true; + this.Logger.LogError(string.Format("Error occurred in GetAllCatalogueSearchResultAsync: {0}", ex.Message)); + return searchViewModel; + } + } + /// /// The RemoveHtmlTags. /// diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/catalogue.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/catalogue.scss index 22ffd1a18..dd64e5a88 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/catalogue.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/catalogue.scss @@ -173,4 +173,15 @@ textarea { font-weight: 700; line-height: 24px; word-wrap: break-word -} \ No newline at end of file +} +.allCatalogue-lettercard { + background: $nhsuk-blue !important; + padding: 12px 8px; + margin-left: 16px; + width: 52px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + color: $nhsuk-white !important; +} diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml new file mode 100644 index 000000000..b6c853dd4 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml @@ -0,0 +1,137 @@ +@using LearningHub.Nhs.WebUI.Extensions +@using Microsoft.AspNetCore.WebUtilities +@model LearningHub.Nhs.Models.Catalogue.AllCatalogueResponseViewModel; + +@{ + ViewData["Title"] = "All Catalogues"; +} + +@section styles { + + +} +
+
+

+ A-Z of catalogues +

+ @*

A-Z of catalogues

*@ + +
+
+
+ @await Html.PartialAsync("_AllCatalogueSearchBar", Model) +
+
+ +
+ +
+
+ @*
+
+

@Model.FilterChar

+
+ +
*@ +
+

@Model.FilterChar

+ +
+
+ +
    + + @foreach (var item in Model.Catalogues) + { +
  • + +
    + +
    + @if (!string.IsNullOrWhiteSpace(item.CardImageUrl)) + { + @item.Name + } + else if (!string.IsNullOrWhiteSpace(item.BannerUrl)) + { + @item.Name + } + else + { +
    + } +
    + +
    + +

    + @item.Name +

    + +
    +
    + @Html.Raw(item.Description) +
    + +
    +
    +
    + @if (item.RestrictedAccess) + { +
    @((item.HasAccess || this.User.IsInRole("Administrator")) ? "Access Granted" : "Access restricted")
    + } +
    + +
    +
    + @if (!string.IsNullOrEmpty(item.BadgeUrl)) + { + Provider's catalogue badge + } +
    +
    +
    +
    +
    + +
  • + } +
+ + @await Html.PartialAsync("_AllCataloguePagination", Model) + +
+
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml new file mode 100644 index 000000000..e493b6683 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml @@ -0,0 +1,114 @@ +@using LearningHub.Nhs.WebUI.Extensions +@using Microsoft.AspNetCore.WebUtilities +@model LearningHub.Nhs.Models.Catalogue.AllCatalogueSearchResponseViewModel; + +@{ + ViewData["Title"] = "All Catalogues Search"; + + var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); + var hasSearchTerm = queryParams.ContainsKey("term"); + var searchTerm = hasSearchTerm ? queryParams["term"].ToString() : null; +} + +@section styles { + +} + +
+ @if (hasSearchTerm) + { + var parms = new Dictionary { { "term", searchTerm } }; + + } + else + { + + } +

+ @(hasSearchTerm ? $"Search results for {searchTerm}" : "All catalogues") +

+ +

+ @($"{Model.TotalCount} catalogue results") +

+ + +
    + + @foreach (var item in Model.Catalogues) + { +
  • + +
    + +
    + @if (!string.IsNullOrWhiteSpace(item.CardImageUrl)) + { + @item.Name + } + else if (!string.IsNullOrWhiteSpace(item.BannerUrl)) + { + @item.Name + } + else + { +
    + } +
    + +
    + +

    + @item.Name +

    + +
    +
    + @Html.Raw(item.Description) +
    + +
    +
    +
    + @if (item.RestrictedAccess) + { +
    @((item.HasAccess || this.User.IsInRole("Administrator")) ? "Access Granted" : "Access restricted")
    + } +
    + +
    +
    + @if (!string.IsNullOrEmpty(item.BadgeUrl)) + { + Provider's catalogue badge + } +
    +
    +
    +
    +
    + +
  • + } +
+ @if (Model.TotalCount > 9) + { + var currentPage = this.ViewBag.PageIndex; + int totalPage = (Model.TotalCount / 9) + (Model.TotalCount % 9 == 0 ? 0 : 1); + var searchQueryParam = hasSearchTerm ? $"&term={searchTerm}" : string.Empty; + var prevUrl = $"/allcataloguesearch?pageindex={currentPage - 1}{searchQueryParam}"; + var nextUrl = $"/allcataloguesearch?pageindex={currentPage + 1}{searchQueryParam}"; + + @await Html.PartialAsync("_Pagination", new PaginationViewModel(currentPage, totalPage, prevUrl, nextUrl)) + } +
\ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCataloguePagination.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCataloguePagination.cshtml new file mode 100644 index 000000000..216facb96 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCataloguePagination.cshtml @@ -0,0 +1,36 @@ +@model LearningHub.Nhs.Models.Catalogue.AllCatalogueResponseViewModel; + +@if (Model.PrevChar != null || Model.NextChar != null) +{ + +} diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml new file mode 100644 index 000000000..d86fdaeb4 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml @@ -0,0 +1,13 @@ +@model LearningHub.Nhs.Models.Catalogue.AllCatalogueResponseViewModel; + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CatalogueTray.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CatalogueTray.cshtml index 9b3bf931d..fd8932610 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CatalogueTray.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CatalogueTray.cshtml @@ -73,7 +73,11 @@
-
+
+ @if (Model.Catalogues.TotalCount > 0 ) + { + View all catalogues + }
diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Default.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Default.cshtml index 719fb24be..4f27477d5 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Default.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Default.cshtml @@ -15,6 +15,15 @@ @if (Context.Request.Path.Value != "/Home/Error" && !SystemOffline()) { + @if (Model.ShowBrowseCatalogues) + { +
  • + + Browse catalogues + + +
  • + } @if (Model.ShowMyLearning) {
  • diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index f7d2a80cf..af3d9e063 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -97,7 +97,8 @@ }, "FindwiseSettings": { "ResourceSearchPageSize": 10, - "CatalogueSearchPageSize": 3 + "CatalogueSearchPageSize": 3, + "AllCatalogueSearchPageSize": 10 }, "MediaKindSettings": { "StorageAccountName": "", @@ -111,7 +112,8 @@ "MediaKindStorageConnectionString": "" }, "EnableTempDebugging": "false", - "LimitScormToAdmin": "false" + "LimitScormToAdmin": "false", + "AllCataloguePageSize": 10 }, "LearningHubAuthServiceConfig": { "Authority": "", diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 12c0510c1..2508d551b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -17,6 +17,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index 04fdc5493..1e4323f1c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -23,6 +23,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 8b4bb1c88..f9f82de35 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 2702cc7e4..d2ab37b52 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -18,6 +18,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/CatalogueController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/CatalogueController.cs index 4c5d06ea8..b5dbf58a9 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/CatalogueController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/CatalogueController.cs @@ -371,5 +371,19 @@ public async Task AccessRequest(int accessRequestId) { return this.Ok(await this.catalogueService.AccessRequestAsync(this.CurrentUserId, accessRequestId)); } + + /// + /// Gets AllCatalogues. + /// + /// The pageSize. + /// The filterChar. + /// IActionResult. + [HttpGet] + [Route("allcatalogues/{pageSize}/{filterChar}")] + public async Task GetAllCataloguesAsync(int pageSize, string filterChar = null) + { + var response = await this.catalogueService.GetAllCataloguesAsync(pageSize, filterChar, this.CurrentUserId); + return this.Ok(response); + } } } diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs index 424e54b0f..9d046bc7b 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs @@ -211,6 +211,19 @@ public async Task CreateCatalogueSearchTermAction(CatalogueSearch } } + /// + /// Get AllCatalogue search result. + /// + /// The catalogue search request model. + /// The . + [HttpPost] + [Route("GetAllCatalogueSearchResult")] + public async Task GetAllCatalogueSearchResult(AllCatalogueSearchRequestModel catalogueSearchRequestModel) + { + var vm = await this.GetAllCatalogueResults(catalogueSearchRequestModel); + return this.Ok(vm); + } + /// /// Get search result. /// @@ -382,5 +395,77 @@ private async Task GetCatalogueSearchResults(Catalogue return searchViewModel; } + + /// + /// Get All catalogue search results. + /// + /// The catalog search request model. + /// The . + private async Task GetAllCatalogueResults(AllCatalogueSearchRequestModel catalogueSearchRequestModel) + { + var results = await this.searchService.GetAllCatalogueSearchResultsAsync(catalogueSearchRequestModel); + + var documents = results.DocumentList.Documents.ToList(); + var documentIds = documents.Select(x => int.Parse(x.Id)).ToList(); + var catalogues = this.catalogueService.GetCataloguesByNodeId(documentIds); + var bookmarks = this.bookmarkRepository.GetAll().Where(b => documentIds.Contains(b.NodeId ?? -1) && b.UserId == this.CurrentUserId); + var allProviders = await this.providerService.GetAllAsync(); + + foreach (var document in documents) + { + var catalogue = catalogues.SingleOrDefault(x => x.NodeId == int.Parse(document.Id)); + if (catalogue == null) + { + continue; + } + + var roleUserGroups = this.catalogueService.GetRoleUserGroupsForCatalogue(catalogue.NodeId, true); + + // catalogue.No + document.Url = catalogue.Url; + document.BannerUrl = catalogue.BannerUrl; + document.BadgeUrl = catalogue.BadgeUrl; + document.CardImageUrl = catalogue.CardImageUrl; + document.NodePathId = catalogue.NodePathId; + + if (catalogue.RestrictedAccess) + { + document.RestrictedAccess = catalogue.RestrictedAccess; + document.HasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) + && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); + } + + var bookmark = bookmarks.FirstOrDefault(x => x.NodeId == int.Parse(document.Id)); + if (bookmark != null) + { + document.BookmarkId = bookmark?.Id; + document.IsBookmarked = !bookmark?.Deleted ?? false; + } + + if (document.ProviderIds?.Count > 0) + { + document.Providers = allProviders.Where(n => document.ProviderIds.Contains(n.Id)).ToList(); + } + } + + var searchViewModel = new SearchAllCatalogueViewModel + { + DocumentModel = documents, + SearchString = catalogueSearchRequestModel.SearchText, + Hits = results.DocumentList.Documents.Count(), + DescriptionMaximumLength = this.settings.Findwise.MaximumDescriptionLength, + ErrorOnAPI = results.ErrorsOnAPICall, + Facets = results.Facets, + }; + + if (results.Stats != null) + { + searchViewModel.TotalHits = results.Stats.TotalHits; + } + + searchViewModel.SearchId = catalogueSearchRequestModel.SearchId > 0 ? catalogueSearchRequestModel.SearchId : results.SearchId; + + return searchViewModel; + } } } \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCatalogues.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCatalogues.sql new file mode 100644 index 000000000..64eb0d38c --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCatalogues.sql @@ -0,0 +1,39 @@ +CREATE PROCEDURE [hierarchy].[GetCatalogues] ( + @userId INT + ,@filterChar nvarchar(10) + ,@OffsetRows int + ,@fetchRows int + ) +AS +BEGIN + + SELECT + nv.NodeId + ,cnv.Id AS NodeVersionId + ,cnv.Name + ,cnv.Description + ,cnv.BannerUrl + ,cnv.BadgeUrl + ,cnv.CardImageUrl + ,cnv.Url + ,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 + FROM [hierarchy].[Node] n + JOIN [hierarchy].[NodeVersion] nv ON nv.NodeId = n.Id + JOIN [hierarchy].[CatalogueNodeVersion] cnv ON cnv.NodeVersionId = nv.Id + LEFT JOIN hub.UserBookmark ub ON ub.UserId = @userId AND ub.NodeId = nv.NodeId + 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 + WHERE n.Id <> 1 AND n.Hidden = 0 AND n.Deleted = 0 AND cnv.Deleted = 0 AND nv.VersionStatusId = 2 + and cnv.Name like @filterChar+'%' + ORDER BY cnv.Name + OFFSET @OffsetRows ROWS + FETCH NEXT @FetchRows ROWS ONLY + + + + +END diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCataloguesCount.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCataloguesCount.sql new file mode 100644 index 000000000..eb22506d0 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetCataloguesCount.sql @@ -0,0 +1,61 @@ +CREATE PROCEDURE [hierarchy].[GetCataloguesCount] ( + @userId INT + ) +AS +BEGIN + + WITH Catalogues AS( + SELECT cnv.Name as Name + FROM [hierarchy].[Node] n + JOIN [hierarchy].[NodeVersion] nv ON nv.NodeId = n.Id + JOIN [hierarchy].[CatalogueNodeVersion] cnv ON cnv.NodeVersionId = nv.Id + LEFT JOIN hub.UserBookmark ub ON ub.UserId = @userId AND ub.NodeId = nv.NodeId + 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 + WHERE n.Id <> 1 AND n.Hidden = 0 AND n.Deleted = 0 AND cnv.Deleted = 0 AND nv.VersionStatusId = 2 ), + + +Alphabet AS ( + SELECT 'A' AS Letter UNION ALL + SELECT 'B' UNION ALL + SELECT 'C' UNION ALL + SELECT 'D' UNION ALL + SELECT 'E' UNION ALL + SELECT 'F' UNION ALL + SELECT 'G' UNION ALL + SELECT 'H' UNION ALL + SELECT 'I' UNION ALL + SELECT 'J' UNION ALL + SELECT 'K' UNION ALL + SELECT 'L' UNION ALL + SELECT 'M' UNION ALL + SELECT 'N' UNION ALL + SELECT 'O' UNION ALL + SELECT 'P' UNION ALL + SELECT 'Q' UNION ALL + SELECT 'R' UNION ALL + SELECT 'S' UNION ALL + SELECT 'T' UNION ALL + SELECT 'U' UNION ALL + SELECT 'V' UNION ALL + SELECT 'W' UNION ALL + SELECT 'X' UNION ALL + SELECT 'Y' UNION ALL + SELECT 'Z' UNION ALL + SELECT '0-9' +) +SELECT + Alphabet.Letter AS Alphabet, + COALESCE(COUNT(cnv.Name), 0) AS Count + FROM Alphabet + LEFT JOIN Catalogues cnv + ON + ((LEFT(cnv.Name, 1) = Alphabet.Letter AND LEFT(cnv.Name, 1) BETWEEN 'A' AND 'Z') + OR (Alphabet.Letter = '0-9' AND LEFT(cnv.Name, 1) BETWEEN '0' AND '9') + ) + + GROUP BY Alphabet.Letter + ORDER BY (CASE WHEN Alphabet.Letter like '[a-z]%' THEN 0 ELSE 1 END), Alphabet.Letter; + +END diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/Hierarchy/ICatalogueNodeVersionRepository.cs b/WebAPI/LearningHub.Nhs.Repository.Interface/Hierarchy/ICatalogueNodeVersionRepository.cs index eb3eed968..1b68a3aef 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/Hierarchy/ICatalogueNodeVersionRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/Hierarchy/ICatalogueNodeVersionRepository.cs @@ -121,5 +121,21 @@ public interface ICatalogueNodeVersionRepository : IGenericRepositoryThe catalogue name. /// The catalogue's node id. Task GetNodeIdByCatalogueName(string catalogueName); + + /// + /// Gets the catalogues count in alphabet list. + /// + /// The userId. + /// The catalogues alphabet count list. + List GetAllCataloguesAlphaCount(int userId); + + /// + /// Gets catalogues based on filter character. + /// + /// The pageSize. + /// The filterChar. + /// The userId. + /// The catalogues. + Task> GetAllCataloguesAsync(int pageSize, string filterChar, int userId); } } diff --git a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs index bc8feee2d..e65bd58ef 100644 --- a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs @@ -355,5 +355,38 @@ join nv in this.DbContext.NodeVersion.AsNoTracking() on cnv.NodeVersionId equals where cnv.Name == catalogueName && cnv.Deleted == false select nv.NodeId).FirstOrDefaultAsync(); } + + /// + /// Gets catalogues count based on alphabets. + /// + /// The userId. + /// resources. + public List GetAllCataloguesAlphaCount(int userId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + + var result = this.DbContext.AllCatalogueAlphabetModel.FromSqlRaw("[hierarchy].[GetCataloguesCount] @userid", param0) + .AsNoTracking().ToList(); + return result; + } + + /// + /// Gets catalogues based on filter character. + /// + /// The pageSize. + /// The filterChar. + /// The userId. + /// resources. + public async Task> GetAllCataloguesAsync(int pageSize, string filterChar, int userId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@filterChar", SqlDbType.NVarChar, 10) { Value = filterChar.Trim() }; + var param2 = new SqlParameter("@OffsetRows", SqlDbType.Int) { Value = 0 }; + var param3 = new SqlParameter("@fetchRows", SqlDbType.Int) { Value = pageSize }; + + var result = await this.DbContext.AllCatalogueViewModel.FromSqlRaw("[hierarchy].[GetCatalogues] @userId, @filterChar, @OffsetRows, @fetchRows", param0, param1, param2, param3) + .AsNoTracking().ToListAsync(); + return result; + } } } diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs b/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs index ef4775cd8..1d94dc9b6 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHubDbContext.cs @@ -770,6 +770,16 @@ public LearningHubDbContextOptions Options /// public virtual DbSet MyLearningActivity { get; set; } + /// + /// Gets or sets the AllCatalogueAlphabet. + /// + public virtual DbSet AllCatalogueAlphabetModel { get; set; } + + /// + /// Gets or sets the AllCatalogueAlphabet. + /// + public virtual DbSet AllCatalogueViewModel { get; set; } + /// /// The on model creating. /// diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs b/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs index 283ea7ae9..d3d8c6122 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs @@ -238,5 +238,14 @@ public interface ICatalogueService /// The catalogueAccessRequestId. /// The catalogue access request. Task AccessRequestAsync(int userId, int catalogueAccessRequestId); + + /// + /// GetAllCataloguesAsync. + /// + /// The pageSize. + /// filterChar. + /// userId. + /// The allcatalogue result based on letters. + Task GetAllCataloguesAsync(int pageSize, string filterChar, int userId); } } diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/ISearchService.cs b/WebAPI/LearningHub.Nhs.Services.Interface/ISearchService.cs index ef44e5076..2f1e0a63c 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/ISearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services.Interface/ISearchService.cs @@ -168,5 +168,12 @@ public interface ISearchService /// The . /// Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchActionCatalogueModel); + + /// + /// Gets AllCatalogue search results async. + /// + /// The allcatalog search request model. + /// The . + Task GetAllCatalogueSearchResultsAsync(AllCatalogueSearchRequestModel catalogSearchRequestModel); } } diff --git a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs index a074e2b99..267fd46c7 100644 --- a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs @@ -401,8 +401,7 @@ public List GetRoleUserGroupsForCatalogue(int catalogueNodeId, bo query = query.Include(x => x.UserGroup).ThenInclude(x => x.UserUserGroup); } - return query.Where(x => x.Scope.CatalogueNodeId == catalogueNodeId) - .ToList(); + return query.Where(x => x.Scope.CatalogueNodeId == catalogueNodeId).ToList(); } /// @@ -954,6 +953,64 @@ public async Task AccessRequestAsync(int userId return vm; } + /// + /// GetAllCataloguesAsync. + /// + /// The pageSize. + /// The filterChar. + /// The userId. + /// A representing the result of the asynchronous operation. + public async Task GetAllCataloguesAsync(int pageSize, string filterChar, int userId) + { + var catalogueAlphaCount = this.catalogueNodeVersionRepository.GetAllCataloguesAlphaCount(userId); + var filterCharMod = filterChar.Trim() == "0-9" ? "[0-9]" : filterChar; + var count = catalogueAlphaCount.FirstOrDefault(ca => ca.Alphabet == filterChar.ToUpper()).Count; + string prevChar = null, nextChar = null, curChar = null; + var filterCharIndex = catalogueAlphaCount.FindIndex(ca => ca.Alphabet == filterChar.ToUpper()); + + // check count and assign prev and next letter + if (count != 0) + { + for (int i = 0; i < catalogueAlphaCount.Count; i++) + { + if (i == filterCharIndex && i == 0) + { + prevChar = null; + } + + if (i == filterCharIndex && i == catalogueAlphaCount.Count - 1) + { + nextChar = null; + } + + if (catalogueAlphaCount[i].Count > 0 && i < filterCharIndex) + { + curChar = catalogueAlphaCount[i].Alphabet; + prevChar = curChar; + } + + if (catalogueAlphaCount[i].Count > 0 && i > filterCharIndex) + { + curChar = catalogueAlphaCount[i].Alphabet; + nextChar = curChar; + break; + } + } + } + + var catalogues = await this.catalogueNodeVersionRepository.GetAllCataloguesAsync(pageSize, filterCharMod, userId); + + var response = new AllCatalogueResponseViewModel + { + CataloguesCount = catalogueAlphaCount, + Catalogues = catalogues, + FilterChar = filterChar.ToUpper(), + PrevChar = prevChar, + NextChar = nextChar, + }; + return response; + } + /// /// The RecordNodeActivity. /// diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index 321313bd3..a247d9aa1 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -541,6 +541,53 @@ public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel return await this.SendSearchEventClickAsync(searchClickPayloadModel, false); } + /// + /// Gets AllCatalogue search results from findwise api call. + /// + /// The allcatalog search request model. + /// The . + public async Task GetAllCatalogueSearchResultsAsync(AllCatalogueSearchRequestModel catalogSearchRequestModel) + { + var viewmodel = new SearchAllCatalogueResultModel(); + try + { + var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; + var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); + var request = string.Format( + this.settings.Findwise.UrlSearchComponent + "?offset={1}&hits={2}&q={3}&token={4}", + this.settings.Findwise.CollectionIds.Catalogue, + offset, + catalogSearchRequestModel.PageSize, + this.EncodeSearchText(catalogSearchRequestModel.SearchText), + this.settings.Findwise.Token); + + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.Logger.LogError($"Get AllCatalogue Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}"); + throw new Exception("AccessDenied to FindWise Server"); + } + else + { + var error = response.Content.ReadAsStringAsync().Result.ToString(); + this.Logger.LogError($"Get AllCatalogue Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); + throw new Exception("Error with FindWise Server"); + } + + return viewmodel; + } + catch (Exception) + { + throw; + } + } + /// /// Send search click payload. /// From b6dacefbac844a53999400877afff30e143b4918 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:28:05 +0100 Subject: [PATCH 02/36] modelversionupdate --- AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj | 2 +- LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Repositories.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Repositories.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +- OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.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 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index f222dc100..c5732d1bd 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..eefee07ba 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.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 2508d551b..089e04862 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index 1e4323f1c..5958d6d6f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -23,7 +23,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index f9f82de35..8d7c3e9b4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index d2ab37b52..e4b3f3319 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -18,7 +18,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 191c99713..997ebf853 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..81af9f85a 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..7f61b4ed2 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..d7dd463f0 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..976eaa15a 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..0cdec8f60 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..6c308ed93 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..250b9d5d0 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..5c74ef944 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..838c53b44 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..821bf02f3 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..4f7ff7274 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..e5f14bdcf 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..9f533328f 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 e9e6a42e1b2a23731e0e46969effe2f22955c653 Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Mon, 19 Aug 2024 00:52:10 +0100 Subject: [PATCH 03/36] archive replaced file --- LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs | 5 +++-- LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue | 5 +++-- LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts | 4 ++-- WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs index 0616474ff..e5e8ab35e 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs @@ -2,6 +2,7 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System; using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Resource; @@ -565,9 +566,9 @@ public async Task DeleteResourceProviderAsync(int resourceVersionI /// A representing the asynchronous operation. [HttpPost] [Route("ArchiveResourceFile")] - public ActionResult ArchiveResourceFile(List filePaths) + public ActionResult ArchiveResourceFile(IEnumerable filePaths) { - _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(null, filePaths); }); + _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(null, filePaths.ToList()); }); return this.Ok(); } diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue index 154d78e98..00d740bb2 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue @@ -822,7 +822,7 @@ this.fileUploadRef.value = null; (this.$refs.fileUploader as any).uploadResourceFile(this.file); }, - fileUploadComplete(uploadResult: FileUploadResult) { + async fileUploadComplete(uploadResult: FileUploadResult) { if (!uploadResult.invalid) { if (uploadResult.resourceType != ResourceType.SCORM) { this.$store.commit("setResourceType", uploadResult.resourceType); @@ -841,7 +841,7 @@ } if (this.filePathBeforeFileChange.length > 0) { - this.getResourceFilePath('completed'); + await this.getResourceFilePath('completed'); if (this.filePathBeforeFileChange.length > 0 && this.filePathAfterFileChange.length > 0) { let filePaths = this.filePathBeforeFileChange.filter(item => !this.filePathAfterFileChange.includes(item)); if (filePaths.length > 0) { @@ -1040,6 +1040,7 @@ await resourceData.getObsoleteResourceFile(resource.resourceVersionId).then(response => { if (fileChangeStatus == 'initialised') { this.filePathBeforeFileChange = response; + this.filePathAfterFileChange.length = 0; } else if (fileChangeStatus == 'completed') { this.filePathAfterFileChange = response; diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts index 9127917d3..817ffcf2f 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts @@ -558,8 +558,8 @@ const getObsoleteResourceFile = async function (id: number): Promise { }; const archiveResourceFile = async function (filepaths: string[]): Promise { - const params = {filePaths:filepaths}; - return await AxiosWrapper.axios.post('/api/Resource/DuplicateBlocks', params).then(() => { + + return await AxiosWrapper.axios.post('/api/Resource/ArchiveResourceFile', filepaths).then(() => { return true }).catch(e => { console.log('archiveResourceFile:' + e); diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user index 696cf1eb7..b4b25c980 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user @@ -4,7 +4,7 @@ ProjectDebugger - IIS Local + API IIS ProjectDebugger 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 04/36] 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 05/36] 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 06/36] 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 07/36] 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 08/36] 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 09/36] 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 32046d21f3e3a6d944c6fb2f588872eba44ea10a Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Thu, 22 Aug 2024 06:39:14 +0100 Subject: [PATCH 10/36] case publish error catch --- .../Controllers/Api/ContributeController.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs index 6bfbcca6c..572cd69c9 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs @@ -349,11 +349,18 @@ public async Task PublishResourceVersionAsync([FromBody] PublishVi { if (associatedResource.ResourceType != ResourceTypeEnum.Scorm && associatedResource.ResourceType != ResourceTypeEnum.Html) { + try + { var obsoleteFiles = await this.resourceService.GetObsoleteResourceFile(publishViewModel.ResourceVersionId); if (obsoleteFiles != null && obsoleteFiles.Any()) { - await this.fileService.PurgeResourceFile(null, obsoleteFiles); + _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(null, obsoleteFiles); }); } + } + catch (Exception ex) + { + this.Logger.LogError($"File Archive Error: {ex.Message}", $"ResourceVersionId -{publishViewModel.ResourceVersionId}"); + } } } @@ -707,8 +714,8 @@ private async Task RemoveBlockCollectionFiles(int resourceVersionId, BlockCollec { foreach (var oldblock in existingImages) { - var entry = newBlocks.FirstOrDefault(x => x.BlockType == BlockType.Media && x.MediaBlock != null && x.MediaBlock.MediaType == MediaType.Image && x.MediaBlock.Image != null && (x.MediaBlock?.Image?.File?.FileId == oldblock.MediaBlock?.Image?.File?.FileId || x.MediaBlock?.Image?.File?.FilePath == oldblock.MediaBlock?.Image?.File?.FilePath)); - if (entry == null) + var entry = newBlocks.FirstOrDefault(x => x.BlockType == BlockType.Media && x.MediaBlock != null && x.MediaBlock.MediaType == MediaType.Image && x.MediaBlock.Image != null && (x.MediaBlock?.Image?.File?.FileId == oldblock.MediaBlock?.Image?.File?.FileId || x.MediaBlock?.Image?.File?.FilePath == oldblock.MediaBlock?.Image?.File?.FilePath)); + if (entry == null) { filePaths.Add(oldblock?.MediaBlock?.Image?.File?.FilePath); } @@ -790,8 +797,10 @@ private async Task RemoveBlockCollectionFiles(int resourceVersionId, BlockCollec _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(null, deleteList); }); } } - catch + catch (Exception ex) { + var param = new object[] { resourceVersionId, existingResource, newResource }; + this.Logger.LogError($"BlockCollection Archive Error: {ex.Message}", param); } } From 18b9ef38d10f296120278fad124be0a271a278ef Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Thu, 22 Aug 2024 06:47:43 +0100 Subject: [PATCH 11/36] . --- WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user index b4b25c980..696cf1eb7 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user @@ -4,7 +4,7 @@ ProjectDebugger - API IIS + IIS Local ProjectDebugger From cad0d846b69d85ac11808b32c93286a42f4efc6e Mon Sep 17 00:00:00 2001 From: Anju Jose Date: Mon, 2 Sep 2024 11:22:24 +0100 Subject: [PATCH 12/36] modelversionupdate --- AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj | 2 +- LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Repositories.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Repositories.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +- OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.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 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index c5732d1bd..21ec69f5e 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 eefee07ba..9e46194b1 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.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 089e04862..23bd109b0 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index 5958d6d6f..365865dad 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -23,7 +23,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 8d7c3e9b4..46a5b7559 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index e4b3f3319..81eb8b30e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -18,7 +18,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 997ebf853..073baa1ff 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 81af9f85a..722cefdee 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 7f61b4ed2..d47e3d417 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 d7dd463f0..35d70c810 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 976eaa15a..5e7e65365 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 0cdec8f60..5d2f278d4 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 6c308ed93..9869ac4be 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 250b9d5d0..44fefe844 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 5c74ef944..de8df1a58 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 838c53b44..52929fbd8 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 821bf02f3..a3951c927 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 4f7ff7274..7efd53fab 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 e5f14bdcf..ad73a25cc 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 9f533328f..861270f13 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 fee6969898d51ee5ed469dfc71fe78665c0e7831 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:44:18 +0100 Subject: [PATCH 13/36] Update LearningHub.Nhs.Database.sqlproj --- .../LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index f513b6ec7..f19a40bbe 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -522,6 +522,8 @@ + + @@ -592,4 +594,4 @@ - \ No newline at end of file + From f51b9eb06710dbfdaaff1ae08032d5b54a1238f7 Mon Sep 17 00:00:00 2001 From: Anju Jose Date: Mon, 2 Sep 2024 12:14:00 +0100 Subject: [PATCH 14/36] removed open api changes --- .../LearningHub.Nhs.OpenApi.Repositories.Interface.csproj | 1 - .../LearningHub.Nhs.OpenApi.Repositories.csproj | 1 - .../LearningHub.Nhs.OpenApi.Tests.csproj | 1 - OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj | 1 - 4 files changed, 4 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 23bd109b0..12c0510c1 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -17,7 +17,6 @@ - diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index 365865dad..04fdc5493 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -23,7 +23,6 @@ - diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 46a5b7559..8b4bb1c88 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -10,7 +10,6 @@ - diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 81eb8b30e..2702cc7e4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -18,7 +18,6 @@ - From bdc69c6be4247813ddacb7d56757d2d470817201 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Mon, 2 Sep 2024 12:14:33 +0100 Subject: [PATCH 15/36] 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 600aae96a128286093d7fc29c927305c40fcd680 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Tue, 3 Sep 2024 09:11:06 +0100 Subject: [PATCH 16/36] TD-4037: Fixed Empty search error issue. --- LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 52bb1125d..b4558eac0 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -605,6 +605,11 @@ public async Task GetAllCatalogueSearch(int pageIndex = 1, string BadgeUrl = t.BadgeUrl, }).ToList(); } + else + { + catalogues.TotalCount = 0; + catalogues.Catalogues = new List(); + } this.ViewBag.PageIndex = pageIndex; return this.View("AllCatalogueSearch", catalogues); From 9978f20ecce160e9577e5d73eefb4584ca286408 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Thu, 5 Sep 2024 09:14:30 +0100 Subject: [PATCH 17/36] TD-4692: Fixed Issue not showing the 'elfh' logo & 'Developed with elearning with healthcare' on the catalogue cards showing on 'All catalogues' screen when created new catalogues. --- .../Controllers/CatalogueController.cs | 2 ++ .../Views/Catalogue/AllCatalogue.cshtml | 27 ++++++++++++------- .../Views/Catalogue/AllCatalogueSearch.cshtml | 19 ++++++++++++- .../Views/Catalogue/Catalogues.cshtml | 18 ++++++++++++- .../Search/_CatalogueSearchResult.cshtml | 16 ++++++++++- .../CatalogueService.cs | 4 +++ 6 files changed, 73 insertions(+), 13 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 52bb1125d..83661dd77 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -123,6 +123,7 @@ public async Task Index(int pageIndex = 1, string term = null) BookmarkId = t.BookmarkId, NodeId = int.Parse(t.Id), BadgeUrl = t.BadgeUrl, + Providers = t.Providers, }).ToList(); } else @@ -603,6 +604,7 @@ public async Task GetAllCatalogueSearch(int pageIndex = 1, string BookmarkId = t.BookmarkId, NodeId = int.Parse(t.Id), BadgeUrl = t.BadgeUrl, + Providers = t.Providers, }).ToList(); } diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml index b6c853dd4..118a5c5f8 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml @@ -4,6 +4,7 @@ @{ ViewData["Title"] = "All Catalogues"; + string cardStyle = "card-provider-details--blank"; } @section styles { @@ -15,7 +16,6 @@

    A-Z of catalogues

    - @*

    A-Z of catalogues

    *@
    @@ -33,7 +33,6 @@ {
  • @activity.Alphabet - @* A *@
  • } else @@ -42,7 +41,6 @@ @activity.Alphabet - @* A *@ } } @@ -50,12 +48,6 @@
    - @*
    -
    -

    @Model.FilterChar

    -
    - -
    *@

    @Model.FilterChar

    @@ -84,6 +76,16 @@
    }
    + @if (item.Providers?.Count > 0) + { +
    + @ProviderHelper.GetProviderString(item.Providers.FirstOrDefault().Name) +
    + } + else + { +
    + }
    @@ -117,7 +119,12 @@
    - @if (!string.IsNullOrEmpty(item.BadgeUrl)) + @if (item.Providers?.Count > 0) + { + var provider = item.Providers.First(); + @provider.Name catalogue badge + } + else if (!string.IsNullOrEmpty(item.BadgeUrl)) { Provider's catalogue badge } diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml index e493b6683..a57169918 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml @@ -8,6 +8,7 @@ var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); var hasSearchTerm = queryParams.ContainsKey("term"); var searchTerm = hasSearchTerm ? queryParams["term"].ToString() : null; + string cardStyle = "card-provider-details--blank"; } @section styles { @@ -55,6 +56,16 @@
    }
    + @if (item.Providers?.Count > 0) + { +
    + @ProviderHelper.GetProviderString(item.Providers.FirstOrDefault().Name) +
    + } + else + { +
    + }
    @@ -88,7 +99,13 @@
    - @if (!string.IsNullOrEmpty(item.BadgeUrl)) + + @if (item.Providers?.Count > 0) + { + var provider = item.Providers.First(); + @provider.Name catalogue badge + } + else if (!string.IsNullOrEmpty(item.BadgeUrl)) { Provider's catalogue badge } diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml index 6eda29434..024f01b95 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Catalogues.cshtml @@ -8,6 +8,7 @@ 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{ @@ -55,6 +56,16 @@
    }
    + @if (item.Providers?.Count > 0) + { +
    + @ProviderHelper.GetProviderString(item.Providers.FirstOrDefault().Name) +
    + } + else + { +
    + }
    @@ -88,7 +99,12 @@
    - @if (!string.IsNullOrEmpty(item.BadgeUrl)) + @if (item.Providers?.Count > 0) + { + var provider = item.Providers.First(); + @provider.Name catalogue badge + } + else if (!string.IsNullOrEmpty(item.BadgeUrl)) { Provider's catalogue badge } diff --git a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml index a27a7aba0..56e54eff1 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml @@ -44,6 +44,20 @@
  • +
    + @if (!string.IsNullOrWhiteSpace(item.CardImageUrl)) + { + @item.Name + } + else if (!string.IsNullOrWhiteSpace(item.BannerUrl)) + { + @item.Name + } + else + { +
    + } +
    @if (provider != null) {
    @@ -91,7 +105,7 @@ { @provider.Name catalogue badge } - else @if (hasBadge) + else if (hasBadge) { Provider's catalogue badge } diff --git a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs index 267fd46c7..8510c49e1 100644 --- a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs @@ -999,6 +999,10 @@ public async Task GetAllCataloguesAsync(int pageS } var catalogues = await this.catalogueNodeVersionRepository.GetAllCataloguesAsync(pageSize, filterCharMod, userId); + foreach (var catalogue in catalogues) + { + catalogue.Providers = await this.providerService.GetByCatalogueVersionIdAsync(catalogue.NodeVersionId); + } var response = new AllCatalogueResponseViewModel { From 0d594752497bb58cd282202a59dc88fc3cbbc8a0 Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Thu, 5 Sep 2024 10:34:24 +0100 Subject: [PATCH 18/36] TD-4693 cache fix --- .../Controllers/UserGroupController.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/UserGroupController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/UserGroupController.cs index 3a27787f4..1be7f211c 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/UserGroupController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/UserGroupController.cs @@ -257,6 +257,7 @@ public async Task AddUsersToUserGroup(int userGroupId, string use var vr = await this.userGroupService.AddUsersToUserGroup(userGroupId, userIdList); if (vr.IsValid) { + this.ClearUserCachedPermissions(userIdList); return this.Json(new { success = true, @@ -527,5 +528,16 @@ public async Task UserGroupCatalogues(int id) return this.PartialView("_UserGroupCatalogues", catalogues); } + + private void ClearUserCachedPermissions(string userIdList) + { + if (!string.IsNullOrWhiteSpace(userIdList)) + { + foreach (var userId in userIdList.Split(",")) + { + _ = Task.Run(async () => { await this.userService.ClearUserCachedPermissions(int.Parse(userId)); }); + } + } + } } } \ No newline at end of file From caa4157c65d2fc92bf6fafdaa46b577e484f1a8c Mon Sep 17 00:00:00 2001 From: Arunima George Date: Fri, 6 Sep 2024 12:00:21 +0100 Subject: [PATCH 19/36] TD-4038: Fixed AllCatalogue pagination issue --- LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs | 1 + .../Views/Catalogue/AllCatalogueSearch.cshtml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 7829e68c0..efa8cd4f2 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -614,6 +614,7 @@ public async Task GetAllCatalogueSearch(int pageIndex = 1, string } this.ViewBag.PageIndex = pageIndex; + this.ViewBag.PageSize = allCatalogueSearchPageSize; return this.View("AllCatalogueSearch", catalogues); } } diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml index a57169918..1d3bee421 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml @@ -5,10 +5,11 @@ @{ ViewData["Title"] = "All Catalogues Search"; - var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); + var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString()); var hasSearchTerm = queryParams.ContainsKey("term"); var searchTerm = hasSearchTerm ? queryParams["term"].ToString() : null; string cardStyle = "card-provider-details--blank"; + var pageSize = this.ViewBag.PageSize; } @section styles { @@ -118,10 +119,10 @@
  • } - @if (Model.TotalCount > 9) + @if (Model.TotalCount > pageSize) { var currentPage = this.ViewBag.PageIndex; - int totalPage = (Model.TotalCount / 9) + (Model.TotalCount % 9 == 0 ? 0 : 1); + int totalPage = (Model.TotalCount / pageSize) + (Model.TotalCount % pageSize == 0 ? 0 : 1); var searchQueryParam = hasSearchTerm ? $"&term={searchTerm}" : string.Empty; var prevUrl = $"/allcataloguesearch?pageindex={currentPage - 1}{searchQueryParam}"; var nextUrl = $"/allcataloguesearch?pageindex={currentPage + 1}{searchQueryParam}"; From 79220b0c5a5d0ad95dee3962ae59630dd870774b Mon Sep 17 00:00:00 2001 From: Arunima George Date: Tue, 10 Sep 2024 12:44:37 +0100 Subject: [PATCH 20/36] TD-4703: Fixed unknown error occuring during pagination --- .../Controllers/SearchController.cs | 9 +++------ .../IRoleUserGroupRepository.cs | 8 ++++++++ .../RoleUserGroupRepository.cs | 14 ++++++++++++++ .../ICatalogueService.cs | 8 ++++++++ .../LearningHub.Nhs.Services/CatalogueService.cs | 11 +++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs index 9d046bc7b..ab4e417d9 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs @@ -257,14 +257,13 @@ private async Task GetSearchResults(SearchRequestModel searchRe continue; } - var roleUserGroups = this.catalogueService.GetRoleUserGroupsForCatalogue(catalogue.NodeId, true); - document.CatalogueUrl = catalogue.Url; document.CatalogueBadgeUrl = catalogue.BadgeUrl; document.CatalogueName = catalogue.Name; if (catalogue.RestrictedAccess) { + var roleUserGroups = await this.catalogueService.GetRoleUserGroupsForCatalogueSearch(catalogue.NodeId, this.CurrentUserId); document.CatalogueRestrictedAccess = catalogue.RestrictedAccess; document.CatalogueHasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); @@ -347,8 +346,6 @@ private async Task GetCatalogueSearchResults(Catalogue continue; } - var roleUserGroups = this.catalogueService.GetRoleUserGroupsForCatalogue(catalogue.NodeId, true); - // catalogue.No document.Url = catalogue.Url; document.BannerUrl = catalogue.BannerUrl; @@ -357,6 +354,7 @@ private async Task GetCatalogueSearchResults(Catalogue document.NodePathId = catalogue.NodePathId; if (catalogue.RestrictedAccess) { + var roleUserGroups = await this.catalogueService.GetRoleUserGroupsForCatalogueSearch(catalogue.NodeId, this.CurrentUserId); document.RestrictedAccess = catalogue.RestrictedAccess; document.HasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); @@ -419,8 +417,6 @@ private async Task GetAllCatalogueResults(AllCatalo continue; } - var roleUserGroups = this.catalogueService.GetRoleUserGroupsForCatalogue(catalogue.NodeId, true); - // catalogue.No document.Url = catalogue.Url; document.BannerUrl = catalogue.BannerUrl; @@ -430,6 +426,7 @@ private async Task GetAllCatalogueResults(AllCatalo if (catalogue.RestrictedAccess) { + var roleUserGroups = await this.catalogueService.GetRoleUserGroupsForCatalogueSearch(catalogue.NodeId, this.CurrentUserId); document.RestrictedAccess = catalogue.RestrictedAccess; document.HasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/IRoleUserGroupRepository.cs b/WebAPI/LearningHub.Nhs.Repository.Interface/IRoleUserGroupRepository.cs index 0a807c18d..cdffec209 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/IRoleUserGroupRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/IRoleUserGroupRepository.cs @@ -55,5 +55,13 @@ public interface IRoleUserGroupRepository : IGenericRepository /// The userGroupId. /// A list of RoleUserGroupViewModel. Task> GetRoleUserGroupViewModelsByUserId(int userId); + + /// + /// The get all for search. + /// + /// The catalogueNodeId. + /// The userId. + /// The . + Task> GetAllforSearch(int catalogueNodeId, int userId); } } diff --git a/WebAPI/LearningHub.Nhs.Repository/RoleUserGroupRepository.cs b/WebAPI/LearningHub.Nhs.Repository/RoleUserGroupRepository.cs index 906c335cc..74a284696 100644 --- a/WebAPI/LearningHub.Nhs.Repository/RoleUserGroupRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository/RoleUserGroupRepository.cs @@ -63,6 +63,20 @@ public async Task GetByRoleIdUserGroupIdScopeIdAsync(int roleId, .Where(n => n.Deleted == false); } + /// + /// The get all for Search. + /// + /// The catalogueNodeId. + /// The userId. + /// The . + public async Task> GetAllforSearch(int catalogueNodeId, int userId) + { + return await this.DbContext.RoleUserGroup.Where(rug => rug.Scope.CatalogueNodeId == catalogueNodeId) + .Include(n => n.UserGroup).ThenInclude(u => u.UserUserGroup.Where(p => p.UserId == userId)) + .Include(n => n.Scope).AsNoTracking() + .ToListAsync(); + } + /// /// The get by role id and catalogue id. /// diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs b/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs index d3d8c6122..69dc89908 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services.Interface/ICatalogueService.cs @@ -192,6 +192,14 @@ public interface ICatalogueService /// The roleUserGroups. List GetRoleUserGroupsForCatalogue(int catalogueNodeId, bool includeUser = false); + /// + /// The GetRolesForCatalogueSearch. + /// + /// The catalogueNodeId. + /// The current user. + /// The roleUserGroups. + Task> GetRoleUserGroupsForCatalogueSearch(int catalogueNodeId, int userId); + /// /// The GetLatestCatalogueAccessRequestAsync. /// diff --git a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs index 8510c49e1..f6ca53582 100644 --- a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs @@ -404,6 +404,17 @@ public List GetRoleUserGroupsForCatalogue(int catalogueNodeId, bo return query.Where(x => x.Scope.CatalogueNodeId == catalogueNodeId).ToList(); } + /// + /// The GetRolesForCatalogueSearch. + /// + /// The catalogueNodeId. + /// The current user. + /// The roleUserGroups. + public async Task> GetRoleUserGroupsForCatalogueSearch(int catalogueNodeId, int userId) + { + return await this.roleUserGroupRepository.GetAllforSearch(catalogueNodeId, userId); + } + /// /// The RequestAccessAsync. /// From 405778ad9a81fc7558c83bd6db2068583197fff6 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Tue, 10 Sep 2024 15:38:22 +0100 Subject: [PATCH 21/36] TD-4038: Implemented search bar in AllCatalogue search results page --- .../Views/Catalogue/AllCatalogue.cshtml | 2 +- .../Views/Catalogue/AllCatalogueSearch.cshtml | 37 ++++++++++++++----- .../Catalogue/_AllCatalogueSearchBar.cshtml | 4 +- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml index 118a5c5f8..702e4277c 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogue.cshtml @@ -20,7 +20,7 @@
    - @await Html.PartialAsync("_AllCatalogueSearchBar", Model) + @await Html.PartialAsync("_AllCatalogueSearchBar", string.Empty)
    diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml index 1d3bee421..be167f34f 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/AllCatalogueSearch.cshtml @@ -14,26 +14,31 @@ @section styles { + } -
    @if (hasSearchTerm) { var parms = new Dictionary { { "term", searchTerm } }; } - else - { - - }

    @(hasSearchTerm ? $"Search results for {searchTerm}" : "All catalogues")

    -

    - @($"{Model.TotalCount} catalogue results") -

    - +
    +
    +
    + @await Html.PartialAsync("_AllCatalogueSearchBar", searchTerm) +
    +
    +
    + @if (Model.TotalCount > 0) + { +

    + @($"{Model.TotalCount} catalogue results") +

    + }
      @@ -129,4 +134,18 @@ @await Html.PartialAsync("_Pagination", new PaginationViewModel(currentPage, totalPage, prevUrl, nextUrl)) } + + @if (Model.TotalCount == 0) + { +
      +
      +

      No results found for @searchTerm

      +

      You could try:

      +
        +
      • checking your spelling
      • +
      • searching again using other words
      • +
      +
      +
      + }
    \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml index d86fdaeb4..c41d0ce35 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_AllCatalogueSearchBar.cshtml @@ -1,8 +1,8 @@ -@model LearningHub.Nhs.Models.Catalogue.AllCatalogueResponseViewModel; +@model string
    @@ -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 35/36] 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 36/36] 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": {