From 304425cae9b22882b9c480e2374f1b070d68714e Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 16 Apr 2026 16:40:42 +0100 Subject: [PATCH 1/2] fixed the issue which was always returning false --- WebAPI/LearningHub.Nhs.Services/SearchService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index 3680f28c..993448a3 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -408,7 +408,7 @@ public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadM /// private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) { - return await Task.FromResult(false); + return await Task.FromResult(true); } /// @@ -420,7 +420,7 @@ private async Task SendSearchEventClickAsync(SearchClickPayloadModel searc /// private async Task SendAutoSuggestionEventClickAsync(AutoSuggestionClickPayloadModel clickPayloadModel) { - return await Task.FromResult(false); + return await Task.FromResult(true); } private string EncodeSearchText(string searchText) From dc6a8333ce133ee9b43895f91a038568ad63ac74 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 16 Apr 2026 16:50:07 +0100 Subject: [PATCH 2/2] Remove more dependencies --- .../Services/SearchService.cs | 924 ------------------ .../Startup.cs | 3 +- .../Services/Services/SearchServiceTests.cs | 20 +- 3 files changed, 14 insertions(+), 933 deletions(-) delete mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs deleted file mode 100644 index d3eb98d6..00000000 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ /dev/null @@ -1,924 +0,0 @@ -namespace LearningHub.Nhs.OpenApi.Services.Services -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using AutoMapper; - using LearningHub.Nhs.Models.Entities.Activity; - using LearningHub.Nhs.Models.Entities.Resource; - using LearningHub.Nhs.Models.Enums; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Models.Search.SearchClick; - using LearningHub.Nhs.Models.Validation; - using LearningHub.Nhs.Models.ViewModels.Helpers; - using LearningHub.Nhs.OpenApi.Models.Configuration; - using LearningHub.Nhs.OpenApi.Models.ServiceModels.Findwise; - using LearningHub.Nhs.OpenApi.Models.ServiceModels.Resource; - using LearningHub.Nhs.OpenApi.Models.ViewModels; - using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; - using LearningHub.Nhs.OpenApi.Services.Helpers; - using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; - using LearningHub.Nhs.OpenApi.Services.Interface.Services; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; - using Event = LearningHub.Nhs.Models.Entities.Analytics.Event; - - /// - /// The search service. - /// - public class SearchService : ISearchService - { - - /// - /// application id for search. - /// - private const string ApplicationId = "HEE"; - - /// - /// profile type for search. - /// - private const string ProfileType = "SEARCHER"; - - private readonly IEventService eventService; - private readonly ILearningHubService learningHubService; - private readonly IResourceRepository resourceRepository; - private readonly IFindwiseClient findwiseClient; - private readonly ILogger logger; - private readonly FindwiseConfig findwiseConfig; - private readonly IMapper mapper; - - /// - /// Initializes a new instance of the class. - /// The search service. - /// - /// - /// The . - /// - /// - /// The . - /// - /// - /// The . - /// - /// - /// The . - /// - /// Logger. - public SearchService( - ILearningHubService learningHubService, - IEventService eventService, - IFindwiseClient findwiseClient, - IOptions findwiseConfig, - IResourceRepository resourceRepository, - ILogger logger, - IMapper mapper) - { - this.learningHubService = learningHubService; - this.eventService = eventService; - this.findwiseClient = findwiseClient; - this.resourceRepository = resourceRepository; - this.logger = logger; - this.mapper = mapper; - this.findwiseConfig = findwiseConfig.Value; - } - - /// - /// The Get Search Result Async method. - /// - /// The search request model. - /// The user id. - /// Cancellation token. - /// The . - public async Task GetSearchResultAsync(SearchRequestModel searchRequestModel, int userId, CancellationToken cancellationToken = default) - { - SearchResultModel viewmodel = new SearchResultModel(); - - try - { - // e.g. if pagesize is 10, then offset would be 0,10,20,30 - var pageSize = searchRequestModel.PageSize; - var offset = searchRequestModel.PageIndex * pageSize; - - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?offset={1}&hits={2}&q={3}&token={4}", - this.findwiseConfig.CollectionIds.Resource, - offset, - pageSize, - this.EncodeSearchText(searchRequestModel.SearchText) + searchRequestModel.FilterText + searchRequestModel.ResourceAccessLevelFilterText + searchRequestModel.ProviderFilterText, - this.findwiseConfig.Token); - - if (searchRequestModel.CatalogueId.HasValue) - { - request += $"&catalogue_ids={searchRequestModel.CatalogueId}"; - } - - // if sort column is requested - if (!string.IsNullOrEmpty(searchRequestModel.SortColumn)) - { - var sortquery = $"&sort={searchRequestModel.SortColumn}"; - - // if sort direction option is requested - if (!string.IsNullOrEmpty(searchRequestModel.SortDirection)) - { - var sortdirection = searchRequestModel.SortDirection.StartsWith("asc") ? "asc" : "desc"; - sortquery = $"{sortquery}_{sortdirection}"; - } - - request = $"{request}{sortquery}"; - } - - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); - searchRequestModel.TotalNumberOfHits = viewmodel.Stats.TotalHits; - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Get 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 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; - } - } - - /// - /// The Get Catalogue Search Result Async method. - /// - /// - /// The catalog search request model. - /// - /// - /// The user id. - /// - /// Cancellation token. - /// - /// The . - /// - public async Task GetCatalogueSearchResultAsync(CatalogueSearchRequestModel catalogSearchRequestModel, int userId, CancellationToken cancellationToken = default) - { - var viewmodel = new SearchCatalogueResultModel(); - - try - { - // e.g. if pagesize is 3, then offset would be 0,3,6,9 - var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?offset={1}&hits={2}&q={3}&token={4}", - this.findwiseConfig.CollectionIds.Catalogue, - offset, - catalogSearchRequestModel.PageSize, - this.EncodeSearchText(catalogSearchRequestModel.SearchText), - this.findwiseConfig.Token); - - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); - catalogSearchRequestModel.TotalNumberOfHits = viewmodel.Stats.TotalHits; - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Get Catalogue 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 Catalogue Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); - throw new Exception("Error with FindWise Server"); - } - - var remainingItems = catalogSearchRequestModel.TotalNumberOfHits - offset; - var resultsPerPage = remainingItems >= catalogSearchRequestModel.PageSize ? catalogSearchRequestModel.PageSize : remainingItems; - LearningHubValidationResult validationResult = await this.CreateCatalogueSearchTerm(catalogSearchRequestModel, resultsPerPage, userId); - - viewmodel.SearchId = validationResult.CreatedId ?? 0; - - return viewmodel; - } - catch (Exception) - { - throw; - } - } - - /// - /// The create resource search action async. - /// - /// - /// The search action request model. - /// - /// - /// The user id. - /// - /// - /// The . - /// - public async Task CreateResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId) - { - var jsonobj = new - { - searchActionResourceModel.SearchText, - searchActionResourceModel.NodePathId, - searchActionResourceModel.ItemIndex, - searchActionResourceModel.NumberOfHits, - searchActionResourceModel.TotalNumberOfHits, - searchActionResourceModel.ResourceReferenceId, - }; - - var json = JsonConvert.SerializeObject(jsonobj); - - var eventEntity = new Event - { - EventTypeEnum = EventTypeEnum.SearchLaunchResource, - JsonData = json, - UserId = userId, - GroupId = searchActionResourceModel.GroupId, - }; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - /// - /// The create catalogue search action async. - /// - /// - /// The search action request model. - /// - /// - /// The user id. - /// - /// - /// The . - /// - public async Task CreateCatalogueSearchActionAsync(SearchActionCatalogueModel searchActionCatalogueModel, int userId) - { - var jsonobj = new - { - searchActionCatalogueModel.SearchText, - searchActionCatalogueModel.NodePathId, - searchActionCatalogueModel.ItemIndex, - searchActionCatalogueModel.NumberOfHits, - searchActionCatalogueModel.TotalNumberOfHits, - searchActionCatalogueModel.CatalogueId, - }; - - var json = JsonConvert.SerializeObject(jsonobj); - - var eventEntity = new Event(); - eventEntity.EventTypeEnum = EventTypeEnum.SearchLaunchCatalogue; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = searchActionCatalogueModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - /// - /// The create catalogue resource search action async. - /// - /// - /// The search action request model. - /// - /// - /// The user id. - /// - /// - /// The . - /// - public async Task CreateCatalogueResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId) - { - var jsonobj = new - { - searchActionResourceModel.SearchText, - searchActionResourceModel.NodePathId, - searchActionResourceModel.ItemIndex, - searchActionResourceModel.ResourceReferenceId, - searchActionResourceModel.NumberOfHits, - searchActionResourceModel.TotalNumberOfHits, - }; - - var json = JsonConvert.SerializeObject(jsonobj); - - var eventEntity = new Event(); - eventEntity.EventTypeEnum = EventTypeEnum.LaunchCatalogueResource; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = searchActionResourceModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - /// - /// The submt feedback async. - /// - /// The search feedback. - /// The user id. - /// The event id. - public async Task SubmitFeedbackAsync(SearchFeedBackModel searchFeedbackModel, int userId) - { - var jsonobj = new - { - searchFeedbackModel.SearchText, - searchFeedbackModel.Feedback, - searchFeedbackModel.TotalNumberOfHits, - }; - - var json = JsonConvert.SerializeObject(jsonobj); - - var eventEntity = new Event(); - eventEntity.EventTypeEnum = EventTypeEnum.SearchSubmitFeedback; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = searchFeedbackModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - /// - /// Create search term event entry. - /// - /// search request model. - /// user id. - /// The . - public async Task CreateSearchTermEvent(SearchRequestModel searchRequestModel, int userId) - { - // e.g. if pagesize is 10, then offset would be 0,10,20,30 - var pageSize = searchRequestModel.PageSize; - var offset = searchRequestModel.PageIndex * pageSize; - - var remainingItems = searchRequestModel.TotalNumberOfHits - offset; - var resultsPerPage = remainingItems >= pageSize ? pageSize : remainingItems; - - var searchEventModel = this.mapper.Map(searchRequestModel); - searchEventModel.ItemsViewed = resultsPerPage; - var json = JsonConvert.SerializeObject(searchEventModel); - - var eventEntity = new Event(); - eventEntity.EventTypeEnum = searchRequestModel.EventTypeEnum; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = searchRequestModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - /// - /// Create catalogue search term event. - /// - /// catalogue search request model. - /// user id. - /// The . - public async Task CreateCatalogueSearchTermEvent(CatalogueSearchRequestModel catalogueSearchRequestModel, int userId) - { - // e.g. if pagesize is 3, then offset would be 0,3,6,9 - var offset = catalogueSearchRequestModel.PageIndex * catalogueSearchRequestModel.PageSize; - - var remainingItems = catalogueSearchRequestModel.TotalNumberOfHits - offset; - var resultsPerPage = remainingItems >= catalogueSearchRequestModel.PageSize ? catalogueSearchRequestModel.PageSize : remainingItems; - - var searchCatalogueEventModel = this.mapper.Map(catalogueSearchRequestModel); - searchCatalogueEventModel.ItemsViewed = resultsPerPage; - var json = JsonConvert.SerializeObject(searchCatalogueEventModel); - var eventEntity = new Event(); - eventEntity.EventTypeEnum = catalogueSearchRequestModel.EventTypeEnum; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = catalogueSearchRequestModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - - /// - /// The create resource search action async. - /// - /// - /// The search action request model. - /// - /// - /// The . - /// - public async Task SendResourceSearchEventClickAsync(SearchActionResourceModel searchActionResourceModel) - { - var searchClickPayloadModel = this.mapper.Map(searchActionResourceModel); - searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.Resource; - - return await this.SendSearchEventClickAsync(searchClickPayloadModel, true); - } - - /// - /// Create catalogue search term. - /// - /// catalogue search request model. - /// results per page. - /// user id. - /// - /// The . - /// - public async Task CreateCatalogueSearchTerm(CatalogueSearchRequestModel catalogueSearchRequestModel, int resultsPerPage, int userId) - { - var searchCatalogueEventModel = this.mapper.Map(catalogueSearchRequestModel); - searchCatalogueEventModel.ItemsViewed = resultsPerPage; - var json = JsonConvert.SerializeObject(searchCatalogueEventModel); - var eventEntity = new Event(); - eventEntity.EventTypeEnum = catalogueSearchRequestModel.EventTypeEnum; - eventEntity.JsonData = json; - eventEntity.UserId = userId; - eventEntity.GroupId = catalogueSearchRequestModel.GroupId; - - return await this.eventService.CreateAsync(userId, eventEntity); - } - - - - /// - public async Task Search(ResourceSearchRequest query, int? currentUserId) - { - var findwiseResultModel = await this.findwiseClient.Search(query); - - if (findwiseResultModel.FindwiseRequestStatus != FindwiseRequestStatus.Success) - { - return ResourceSearchResultModel.FailedWithStatus(findwiseResultModel.FindwiseRequestStatus); - } - - var resourceMetadataViewModels = await this.GetResourceMetadataViewModels(findwiseResultModel, currentUserId); - - var totalHits = findwiseResultModel.SearchResults?.Stats.TotalHits; - - return new ResourceSearchResultModel( - resourceMetadataViewModels, - findwiseResultModel.FindwiseRequestStatus, - totalHits ?? 0); - } - - /// - /// The remove resource from search async method. - /// - /// The resource to be removed from search. - /// The . - public async Task RemoveResourceFromSearchAsync(int resourceId) - { - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.IndexMethod)) - { - this.logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not removed from search."); - } - else - { - var client = await this.findwiseClient.GetClient(this.findwiseConfig.IndexUrl); - - var request = string.Format(this.findwiseConfig.IndexMethod, this.findwiseConfig.CollectionIds.Resource) + $"?id={resourceId.ToString()}&token={this.findwiseConfig.Token}"; - - var response = await client.DeleteAsync(request).ConfigureAwait(false); - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - throw new Exception("Removal of resource to search failed: " + resourceId.ToString()); - } - } - } - catch (Exception ex) - { - throw new Exception("Removal of resource from search failed: " + resourceId.ToString() + " : " + ex.Message); - } - } - - /// - /// The send resource for search Async method. - /// - /// The resource to be added to search. - /// The user id. - /// number of iterations. - /// The . - public async Task SendResourceForSearchAsync(SearchResourceRequestModel searchResourceRequestModel, int userId, int? iterations) - { - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.IndexMethod)) - { - this.logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not added to search results"); - } - else - { - List resourceList =[searchResourceRequestModel]; - - var json = JsonConvert.SerializeObject(resourceList, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); - - var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.findwiseClient.GetClient(this.findwiseConfig.IndexUrl); - - var request = string.Format(this.findwiseConfig.SearchBaseUrl, this.findwiseConfig.CollectionIds.Resource) + $"?token={this.findwiseConfig.Token}"; - var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); - iterations--; - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - if (iterations < 0) - { - this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("Posting of resource to search failed: " + stringContent); - } - - await this.SendResourceForSearchAsync(searchResourceRequestModel, userId, iterations); - } - else - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - throw new Exception("Posting of resource to search failed: " + searchResourceRequestModel.Id + " : " + ex.Message); - } - } - - /// - /// The create catalogue search action async. - /// - /// - /// The search action request model. - /// - /// - /// The . - /// - public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchActionCatalogueModel) - { - var searchClickPayloadModel = this.mapper.Map(searchActionCatalogueModel); - searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.Catalogue; - - 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.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?offset={1}&hits={2}&q={3}&token={4}", - this.findwiseConfig.CollectionIds.Catalogue, - offset, - catalogSearchRequestModel.PageSize, - this.EncodeSearchText(catalogSearchRequestModel.SearchText), - this.findwiseConfig.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; - } - } - - /// - /// The Get Auto suggestion Results Async method. - /// - /// The term. - /// Cancellation token. - /// The . - public async Task GetAutoSuggestionResultsAsync(string term, CancellationToken cancellationToken = default) - { - var viewmodel = new AutoSuggestionModel(); - - try - { - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?q={1}&token={2}", - this.findwiseConfig.CollectionIds.AutoSuggestion, - this.EncodeSearchText(term), - this.findwiseConfig.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 Auto Suggetion 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 Auto Suggetion Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); - throw new Exception("Error with FindWise Server"); - } - - return viewmodel; - } - catch (Exception) - { - throw; - } - } - - - /// - /// The AutoSuggestion Event action async. - /// - /// - /// The clic kPayload Model. - /// - /// - /// The . - /// - public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadModel clickPayloadModel) - { - clickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - clickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; - clickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - clickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.AutoSuggestion; - - return await this.SendAutoSuggestionEventClickAsync(clickPayloadModel); - } - - /// - /// Send search click payload. - /// - /// search click payload model. - /// isResource. - /// - /// The . - /// - private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) - { - var eventType = isResource ? "resource" : "catalog"; - - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.UrlClickComponent)) - { - this.logger.LogWarning($"The UrlClickComponent is not configured. {eventType} click event not send to FindWise."); - } - else - { - var json = JsonConvert.SerializeObject(searchClickPayloadModel); - var base64EncodedString = BinaryFormatterHelper.Base64EncodeObject(json); - - var request = $"{this.findwiseConfig.UrlClickComponent}?payload={base64EncodedString}"; - - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var response = await client.PostAsync(request, null).ConfigureAwait(false); - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - this.logger.LogError($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); - throw new Exception($"Click event save to FindWise failed for {eventType}: {json}"); - } - else - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - throw new Exception($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} : {ex.Message}"); - } - } - - /// - /// Send auto suggestion click payload. - /// - /// search click payload model. - /// - /// The . - /// - private async Task SendAutoSuggestionEventClickAsync(AutoSuggestionClickPayloadModel clickPayloadModel) - { - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.UrlAutoSuggestionClickComponent)) - { - this.logger.LogWarning($"The UrlClickComponent is not configured. Auto suggestion click event not send to FindWise."); - } - else - { - var json = JsonConvert.SerializeObject(clickPayloadModel); - var base64EncodedString = BinaryFormatterHelper.Base64EncodeObject(json); - - var request = $"{this.findwiseConfig.UrlAutoSuggestionClickComponent}?payload={base64EncodedString}"; - - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var response = await client.PostAsync(request, null).ConfigureAwait(false); - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - this.logger.LogError($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); - throw new Exception($"Click event save to FindWise failed for Auto suggestion: {json}"); - } - else - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - throw new Exception($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} : {ex.Message}"); - } - } - - - private string EncodeSearchText(string searchText) - { - string specialSearchCharacters = this.findwiseConfig.SpecialSearchCharacters; - - // Add backslash to the start of the string - specialSearchCharacters = specialSearchCharacters.Replace(@"\", null); - specialSearchCharacters = @"\" + specialSearchCharacters; - - for (int i = 0; i < specialSearchCharacters.Length; i++) - { - searchText = searchText.Replace(specialSearchCharacters[i].ToString(), @"\" + specialSearchCharacters[i]); - } - - searchText = HttpUtility.UrlEncode(searchText); - return searchText; - } - - private async Task> GetResourceMetadataViewModels( - FindwiseResultModel findwiseResultModel, int? currentUserId) - { - List resourceActivities = new List() { }; - List resourceMetadataViewModels = new List() { }; - var documentsFound = findwiseResultModel.SearchResults?.DocumentList.Documents?.ToList() ?? - new List(); - var findwiseResourceIds = documentsFound.Select(d => int.Parse(d.Id)).ToList(); - - if (!findwiseResourceIds.Any()) - { - return new List(); - } - - var resourcesFound = await this.resourceRepository.GetResourcesFromIds(findwiseResourceIds); - - 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() { }; - } - - resourceMetadataViewModels = resourcesFound.Select(resource => this.MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) - .OrderBySequence(findwiseResourceIds) - .ToList(); - - var unmatchedResources = findwiseResourceIds - .Except(resourceMetadataViewModels.Select(r => r.ResourceId)).ToList(); - - if (unmatchedResources.Any()) - { - var unmatchedResourcesIdsString = string.Join(", ", unmatchedResources); - this.logger.LogWarning( - "Findwise returned documents that were not found in the database with IDs: " + - unmatchedResourcesIdsString); - } - - return resourceMetadataViewModels; - } - - 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( - $"Resource with id {resource.Id} is missing a current resource version"); - } - - if (!hasRating) - { - this.logger.LogInformation( - $"Resource with id {resource.Id} is missing a ResourceVersionRatingSummary"); - } - - var resourceTypeNameOrEmpty = resource.GetResourceTypeNameOrEmpty(); - if (resourceTypeNameOrEmpty == string.Empty) - { - 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?.MajorVersion ?? 0, - resource.CurrentResourceVersion?.ResourceVersionRatingSummary?.AverageRating ?? 0.0m, - majorVersionIdActivityStatusDescription - ); - } - - private ResourceReferenceViewModel GetResourceReferenceViewModel( - ResourceReference resourceReference) - { - return new ResourceReferenceViewModel( - resourceReference.OriginalResourceReferenceId, - resourceReference.GetCatalogue(), - this.learningHubService.GetResourceLaunchUrl(resourceReference.OriginalResourceReferenceId)); - } - } -} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index c2e0ae59..56b9450e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -33,8 +33,7 @@ public static void AddServices(this IServiceCollection services, IConfiguration } else { - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } services.AddHttpClient(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs index d8d0568b..bc4f1407 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs @@ -20,6 +20,7 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using LearningHub.Nhs.OpenApi.Services.Services; + using LearningHub.Nhs.OpenApi.Services.Services.AzureSearch; using LearningHub.Nhs.OpenApi.Tests.TestHelpers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -33,11 +34,13 @@ public class SearchServiceTests private readonly Mock learningHubService; private readonly Mock resourceRepository; private readonly Mock resourceService; + private readonly Mock cachingService; private readonly Mock eventService; - private readonly SearchService searchService; + private readonly AzureSearchService searchService; private readonly Mock> findwiseConfig; - private Mock> mockLogger; + private Mock> mockLogger; private readonly Mock mapper; + private readonly Mock> azureSearchConfig; public SearchServiceTests() { @@ -45,19 +48,22 @@ public SearchServiceTests() this.learningHubService = new Mock(); this.resourceRepository = new Mock(); this.resourceService = new Mock(); + this.cachingService = new Mock(); + this.azureSearchConfig = new Mock>(); this.eventService = new Mock(); this.mapper = new Mock(); this.findwiseConfig = new Mock>(); - this.mockLogger = new Mock>(); - this.searchService = new SearchService( + this.mockLogger = new Mock>(); + this.searchService = new AzureSearchService( this.learningHubService.Object, this.eventService.Object, - this.findwiseClient.Object, - this.findwiseConfig.Object, + this.azureSearchConfig.Object, this.resourceRepository.Object, + this.cachingService.Object, this.mockLogger.Object, - this.mapper.Object); + this.mapper.Object + ); } public static IEnumerable TestFindwiseResultModel => new[]