From 9f4ff0a398d1113cf9e28462a12f1013a354b48a Mon Sep 17 00:00:00 2001 From: Chad Date: Wed, 4 May 2022 07:13:27 +1200 Subject: [PATCH] async tree search (#12344) --- .../ContentEditing/EntitySearchResults.cs | 25 ++++++++++++++ .../ContentEditing/SearchResultEntity.cs | 4 ++- src/Umbraco.Core/Trees/ISearchableTree.cs | 3 +- .../Controllers/EntityController.cs | 34 +++++++++++++------ .../Trees/ContentTreeController.cs | 6 ++-- .../Trees/ContentTypeTreeController.cs | 8 +++-- .../Trees/DataTypeTreeController.cs | 8 +++-- .../Trees/MediaTreeController.cs | 6 ++-- .../Trees/MediaTypeTreeController.cs | 9 +++-- .../Trees/MemberTreeController.cs | 6 ++-- .../Trees/MemberTypeTreeController.cs | 9 +++-- .../Trees/TemplatesTreeController.cs | 8 +++-- 12 files changed, 96 insertions(+), 30 deletions(-) create mode 100644 src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs diff --git a/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs new file mode 100644 index 0000000000..ff77e3aeb5 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models.ContentEditing +{ + + [DataContract(Name = "searchResults", Namespace = "")] + public class EntitySearchResults : IEnumerable + { + private readonly IEnumerable _results; + + public EntitySearchResults(IEnumerable results, long totalFound) + { + _results = results; + TotalResults = totalFound; + } + + [DataMember(Name = "totalResults")] + public long TotalResults { get; set; } + + public IEnumerator GetEnumerator() => _results.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_results).GetEnumerator(); + } +} diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs index 5307e06e67..e2fc1ff2d7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs @@ -1,4 +1,6 @@ -using System.Runtime.Serialization; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing { diff --git a/src/Umbraco.Core/Trees/ISearchableTree.cs b/src/Umbraco.Core/Trees/ISearchableTree.cs index 018aa88ae5..dd61ba0cdb 100644 --- a/src/Umbraco.Core/Trees/ISearchableTree.cs +++ b/src/Umbraco.Core/Trees/ISearchableTree.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.ContentEditing; @@ -22,6 +23,6 @@ namespace Umbraco.Cms.Core.Trees /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// - IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null); + Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index a31ee6b27e..a3716f53aa 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Dynamic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -193,9 +195,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// methods might be used in things like pickers in the content editor. /// [HttpGet] - public IDictionary SearchAll(string query) + public async Task> SearchAll(string query) { - var result = new Dictionary(); + var result = new ConcurrentDictionary(); if (string.IsNullOrEmpty(query)) { @@ -204,6 +206,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.AllowedSections.ToArray(); + var searchTasks = new List(); foreach (KeyValuePair searchableTree in _searchableTreeCollection .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) { @@ -218,21 +221,32 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var rootNodeDisplayName = Tree.GetRootNodeDisplayName(tree, _localizedTextService); if (rootNodeDisplayName is not null) { - result[rootNodeDisplayName] = new TreeSearchResult - { - Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out var total).WhereNotNull(), - TreeAlias = searchableTree.Key, - AppAlias = searchableTree.Value.AppAlias, - JsFormatterService = searchableTree.Value.FormatterService, - JsFormatterMethod = searchableTree.Value.FormatterMethod - }; + searchTasks.Add(ExecuteSearchAsync(query, searchableTree, rootNodeDisplayName,result)); } } } + await Task.WhenAll(searchTasks); return result; } + private static async Task ExecuteSearchAsync( + string query, + KeyValuePair searchableTree, + string rootNodeDisplayName, + ConcurrentDictionary result) + { + var searchResult = new TreeSearchResult + { + Results = (await searchableTree.Value.SearchableTree.SearchAsync(query, 200, 0)).WhereNotNull(), + TreeAlias = searchableTree.Key, + AppAlias = searchableTree.Value.AppAlias, + JsFormatterService = searchableTree.Value.FormatterService, + JsFormatterMethod = searchableTree.Value.FormatterMethod + }; + + result.AddOrUpdate(rootNodeDisplayName, _=> searchResult, (_,_) => searchResult); + } /// /// Gets the path for a given node ID /// diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 5b51ddda55..59b386fe8e 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -366,9 +367,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var menuItem = menu.Items.Add(LocalizedTextService, hasSeparator, opensDialog); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out totalFound, searchFrom); + var results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index a688713109..d0a01559fb 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -180,8 +181,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) - => _treeSearcher.EntitySearch(UmbracoObjectTypes.DocumentType, query, pageSize, pageIndex, out totalFound, searchFrom); + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) + { + var results = _treeSearcher.EntitySearch(UmbracoObjectTypes.DocumentType, query, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 408eca9556..38bdce4665 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -170,7 +171,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) - => _treeSearcher.EntitySearch(UmbracoObjectTypes.DataType, query, pageSize, pageIndex, out totalFound, searchFrom); + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) + { + var results = _treeSearcher.EntitySearch(UmbracoObjectTypes.DataType, query, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index b8c057e243..2bd171b7c7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -183,9 +184,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return HasPathAccess(entity, queryStrings); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); + var results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index de46d135a3..15684c6cb8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -145,8 +146,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) - => _treeSearcher.EntitySearch(UmbracoObjectTypes.MediaType, query, pageSize, pageIndex, out totalFound, searchFrom); - + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) + { + var results = _treeSearcher.EntitySearch(UmbracoObjectTypes.MediaType, query, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 4b997b44de..88356b7b8a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -156,9 +157,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Member, pageSize, pageIndex, out totalFound, searchFrom); + var results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Member, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index 8e74b6ec80..22d110b4fc 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -63,8 +64,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, dt?.Icon ?? Constants.Icons.MemberType, false)); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) - => _treeSearcher.EntitySearch(UmbracoObjectTypes.MemberType, query, pageSize, pageIndex, out totalFound, searchFrom); - + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) + { + var results = _treeSearcher.EntitySearch(UmbracoObjectTypes.MemberType, query, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); + } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs index a155493aff..fa3e859b6d 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -156,7 +157,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees }; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) - => _treeSearcher.EntitySearch(UmbracoObjectTypes.Template, query, pageSize, pageIndex, out totalFound, searchFrom); + public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) + { + var results = _treeSearcher.EntitySearch(UmbracoObjectTypes.Template, query, pageSize, pageIndex, out long totalFound, searchFrom); + return new EntitySearchResults(results, totalFound); + } } }