new search index extension ability

This commit is contained in:
2021-09-02 22:44:20 +02:00
parent b081f0f63f
commit e1b3ce06af
11 changed files with 275 additions and 104 deletions
+10 -66
View File
@@ -1,78 +1,22 @@
using Raven.Client.Documents.Indexes;
using System.Collections.Generic;
using System.Linq;
using zero.Core.Entities;
using Raven.Client.Documents;
using zero.Core.Options;
namespace zero.Core.Database.Indexes
{
public class Backoffice_Search : ZeroMultiMapIndex
public class Backoffice_Search : ZeroJavascriptIndex
{
public class Result
public override void Setup(IZeroOptions options, IDocumentStore store)
{
public string Id { get; set; }
// TODO index.Conventions is null, but needed for collection name retrieval
public string Group { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public List<string> Fields { get; set; } = new();
}
protected override void Create()
{
AddEntities();
Index(nameof(Result.Name), FieldIndexing.Search);
Index(nameof(Result.Fields), FieldIndexing.Search);
}
protected virtual void AddEntities()
{
AddPages();
AddMediaFolders();
//AddMedia();
}
protected virtual void AddPages()
{
AddMap<Page>(map => map.Select(page => new Result()
foreach (var map in options.Search.GetAllItems())
{
Id = page.Id,
Group = "zero.page",
Name = page.Name,
IsActive = page.IsActive,
Fields = new()
}));
}
Maps.Add(map.BuildInstruction(this, store));
}
protected virtual void AddMediaFolders()
{
AddMap<MediaFolder>(map => map.Select(page => new Result()
{
Id = page.Id,
Group = "zero.mediafolder",
Name = page.Name,
IsActive = page.IsActive,
Fields = new()
}));
}
protected virtual void AddMedia()
{
AddMap<Media>(map => map.Select(page => new Result()
{
Id = page.Id,
Group = "zero.media",
Name = page.Name,
IsActive = page.IsActive,
Fields = new()
}));
//Index(nameof(SearchIndexResult.Name), FieldIndexing.Search);
//Index(nameof(SearchIndexResult.Fields), FieldIndexing.Search);
}
}
}
+18 -16
View File
@@ -1,4 +1,5 @@
using Raven.Client.Documents.Indexes;
using Raven.Client.Documents;
using Raven.Client.Documents.Indexes;
using Raven.Client.Documents.Indexes.Spatial;
using Raven.Client.Documents.Operations.Attachments;
using System;
@@ -12,7 +13,9 @@ namespace zero.Core.Database
public abstract class ZeroJavascriptIndex : AbstractJavaScriptIndexCreationTask, IZeroIndexDefinition
{
public ZeroJavascriptIndex() { Create(); }
protected abstract void Create();
protected virtual void Create() { }
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
public new string Reduce { get => base.Reduce; set => base.Reduce = value; }
public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; }
@@ -47,7 +50,9 @@ namespace zero.Core.Database
public abstract class ZeroMultiMapIndex : AbstractMultiMapIndexCreationTask<object>, IZeroIndexDefinition
{
public ZeroMultiMapIndex() { Create(); }
protected abstract void Create();
protected virtual void Create() { }
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
// AbstractMultiMapIndexCreationTask<TReduceResult>
public new void AddMap<TSource>(Expression<Func<IEnumerable<TSource>, IEnumerable>> map) => base.AddMap(map);
@@ -112,7 +117,9 @@ namespace zero.Core.Database
public abstract class ZeroMultiMapIndex<TReduceResult> : AbstractMultiMapIndexCreationTask<TReduceResult>, IZeroIndexDefinition
{
public ZeroMultiMapIndex() { Create(); }
protected abstract void Create();
protected virtual void Create() { }
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
// AbstractMultiMapIndexCreationTask<TReduceResult>
public new void AddMap<TSource>(Expression<Func<IEnumerable<TSource>, IEnumerable>> map) => base.AddMap(map);
@@ -177,7 +184,9 @@ namespace zero.Core.Database
public abstract class ZeroIndex<TDocument, TReduceResult> : AbstractIndexCreationTask<TDocument, TReduceResult>, IZeroIndexDefinition
{
public ZeroIndex() { Create(); }
protected abstract void Create();
protected virtual void Create() { }
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
// AbstractIndexCreationTask<TDocument, TReduceResult>
public new Expression<Func<IEnumerable<TDocument>, IEnumerable>> Map { get => base.Map; set => base.Map = value; }
@@ -246,7 +255,9 @@ namespace zero.Core.Database
public abstract class ZeroIndex : AbstractIndexCreationTask, IZeroIndexDefinition
{
public ZeroIndex() { Create(); }
protected abstract void Create();
protected virtual void Create() { }
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
// AbstractIndexCreationTask
public new IJsonObject AsJson(object doc) => base.AsJson(doc);
@@ -276,15 +287,6 @@ namespace zero.Core.Database
public interface IZeroIndexDefinition : IAbstractIndexCreationTask
{
void Setup(IZeroOptions options)
{
IEnumerable<RavenIndexModifiersOptions.Modifier> modifiers = options.Raven.Indexes.Modifiers.GetAllForType(this.GetType());
foreach (RavenIndexModifiersOptions.Modifier modifier in modifiers)
{
Action<IZeroIndexDefinition> action = modifier.Modify.Compile();
action.Invoke(this);
}
}
void Setup(IZeroOptions options, IDocumentStore store);
}
}
+37
View File
@@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace zero.Core.Models
{
public class SearchResult
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Icon { get; set; }
public string Image { get; set; }
public string Url { get; set; }
public string Group { get; set; }
public bool IsActive { get; set; }
}
public class SearchIndexResult
{
public string Id { get; set; }
public string Group { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public List<string> Fields { get; set; } = new();
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using zero.Core.Database;
using zero.Core.Options;
namespace zero.Core.Extensions
{
public static class ZeroIndexExtensions
{
internal static void RunModifiers<T>(this T index, IZeroOptions options) where T : IZeroIndexDefinition
{
IEnumerable<RavenIndexModifiersOptions.Modifier> modifiers = options.Raven.Indexes.Modifiers.GetAllForType(index.GetType());
foreach (RavenIndexModifiersOptions.Modifier modifier in modifiers)
{
Action<IZeroIndexDefinition> action = modifier.Modify.Compile();
action.Invoke(index);
}
}
}
}
+41 -13
View File
@@ -1,9 +1,11 @@
using Raven.Client.Documents.Indexes;
using Raven.Client.Documents;
using Raven.Client.Documents.Indexes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using zero.Core.Database;
using zero.Core.Extensions;
namespace zero.Core.Options
{
@@ -19,45 +21,71 @@ namespace zero.Core.Options
}
public class RavenIndexesOptions : ZeroBackofficeCollection<Type>, IZeroCollectionOptions
public class RavenIndexesOptions : ZeroBackofficeCollection<RavenIndexesOptions.Map>, IZeroCollectionOptions
{
public class Map
{
internal Type Type { get; set; }
internal Expression<Func<IZeroIndexDefinition>> CreateIndex { get; set; }
internal Map(Type type, Expression<Func<IZeroIndexDefinition>> create)
{
Type = type;
CreateIndex = create;
}
}
public RavenIndexModifiersOptions Modifiers { get; private set; } = new();
public void Add<T>() where T : IZeroIndexDefinition, new()
{
Items.Add(typeof(T));
Items.Add(new(typeof(T), () => new T()));
}
public void Add(Type indexType)
{
Items.Add(indexType);
Items.Add(new(indexType, () => (IZeroIndexDefinition)Activator.CreateInstance(indexType)));
}
public void Add<T>(T index) where T : IZeroIndexDefinition
{
Items.Add(new(typeof(T), () => index));
}
public void AddRange(params Type[] indexes)
{
Items.AddRange(indexes);
foreach (Type type in indexes)
{
Add(type);
}
}
public void Replace<T, TReplaceWith>()
where T : IZeroIndexDefinition, new()
where TReplaceWith : IZeroIndexDefinition, new()
{
Items.Remove(typeof(T));
Items.Add(typeof(TReplaceWith));
Replace(typeof(T), typeof(TReplaceWith));
}
public void Replace(Type origin, Type replaceWith)
{
Items.Remove(origin);
Items.Add(replaceWith);
var item = Items.FirstOrDefault(x => x.Type == origin);
if (item != null)
{
Items.Remove(item);
}
Add(replaceWith);
}
public IEnumerable<IZeroIndexDefinition> GetAllForRegistration(IZeroOptions options)
public IEnumerable<IZeroIndexDefinition> BuildAll(IZeroOptions options, IDocumentStore store)
{
foreach (Type type in Items)
foreach (Map map in Items)
{
IZeroIndexDefinition index = (IZeroIndexDefinition)Activator.CreateInstance(type);
index.Setup(options);
IZeroIndexDefinition index = map.CreateIndex.Compile().Invoke();
index.Setup(options, store);
index.RunModifiers(options);
yield return index;
}
}
+106
View File
@@ -0,0 +1,106 @@
using Raven.Client.Documents;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using zero.Core.Database;
using zero.Core.Entities;
using zero.Core.Models;
using zero.Core.Renderer;
namespace zero.Core.Options
{
public class SearchOptions : ZeroBackofficeCollection<SearchIndexMap>, IZeroCollectionOptions
{
public bool IsEnabled { get; set; }
public SearchOptions()
{
IsEnabled = true;
Map<Page>();
Map<MediaFolder>();
}
public SearchIndexMap<T> Map<T>(string group = null) where T : ZeroEntity, new()
{
SearchIndexMap<T> map = new(group);
Items.Add(map);
return map;
}
}
public class SearchIndexMap
{
protected Type _type;
protected string _group;
protected string[] _fields;
protected Func<ZeroEntity, SearchResult, Task> _modify;
const string mapTemplate = @"map('{collection}', function (x) {
return {
Id: x.Id,
Group: '{group}',
Name: x.Name,
IsActive: x.IsActive,
Fields: [{fields}]
};
});";
internal SearchIndexMap(Type type, string group)
{
_type = type;
_group = group;
}
internal string BuildInstruction(IZeroIndexDefinition index, IDocumentStore store)
{
return TokenReplacement.Apply(mapTemplate, new()
{
{ "collection", store.Conventions.GetCollectionName(_type) },
{ "group", _group },
{ "fields", BuildFieldArray(_fields) }
});
}
internal string BuildFieldArray(string[] fields)
{
if (fields == null || !fields.Any())
{
return String.Empty;
}
return "x." + String.Join(", x.", fields);
}
}
public class SearchIndexMap<T> : SearchIndexMap where T : ZeroEntity
{
internal SearchIndexMap(string group) : base(typeof(T), group) {}
public SearchIndexMap<T> Display(Action<T, SearchResult> modify = null)
{
_modify = (x, res) =>
{
modify?.Invoke(x as T, res);
return Task.CompletedTask;
};
return this;
}
public SearchIndexMap<T> Display(Func<T, SearchResult, Task> modify = null)
{
_modify = (x, res) => modify?.Invoke(x as T, res);
return this;
}
public SearchIndexMap<T> Fields(params string[] fieldNames)
{
_fields = fieldNames;
return this;
}
}
}
+10
View File
@@ -40,6 +40,8 @@ namespace zero.Core.Options
Raven.Indexes.Add<Pages_ByHierarchy>();
Raven.Indexes.Add<Pages_WithChildren>();
Raven.Indexes.Add<Routes_ForResolver>();
Search = new();
}
/// <inheritdoc />
@@ -106,6 +108,9 @@ namespace zero.Core.Options
/// <inheritdoc />
public InterceptorOptions Interceptors { get; private set; }
/// <inheritdoc />
public SearchOptions Search { get; private set; }
}
@@ -211,5 +216,10 @@ namespace zero.Core.Options
///
/// </summary>
InterceptorOptions Interceptors { get; }
/// <summary>
///
/// </summary>
SearchOptions Search { get; }
}
}
+1 -1
View File
@@ -28,7 +28,7 @@
name: 'app-search',
data: () => ({
open: false,
open: true,
query: null,
model: {
page: 1,
+2 -1
View File
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using zero.Core.Entities;
using zero.Core.Identity;
using zero.Core.Models;
using zero.Web.Services;
namespace zero.Web.Controllers
@@ -16,6 +17,6 @@ namespace zero.Web.Controllers
SearchService = searchService;
}
public async Task<ListResult<ZeroEntity>> Query([FromQuery] string query) => await SearchService.Query(query);
public async Task<ListResult<SearchResult>> Query([FromQuery] string query) => await SearchService.Query(query);
}
}
+28 -6
View File
@@ -1,11 +1,15 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Queries;
using Raven.Client.Documents.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Database;
using zero.Core.Database.Indexes;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Models;
namespace zero.Web.Services
{
@@ -19,22 +23,40 @@ namespace zero.Web.Services
}
public async Task<ListResult<ZeroEntity>> Query(string searchTerm)
public async Task<ListResult<SearchResult>> Query(string searchTerm)
{
List<ZeroEntity> results = await Session.Query<Backoffice_Search.Result, Backoffice_Search>()
string[] searchParts = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x =>
{
return "*" + x + "*";
}).ToArray();
List<ZeroEntity> results = await Session.Query<SearchIndexResult, Backoffice_Search>()
.Statistics(out QueryStatistics stats)
.Search(x => x.Name, searchTerm, 2)
.Search(x => x.Fields, searchTerm, 1, SearchOptions.Or)
.Search(x => x.Name, searchParts, 2, @operator: SearchOperator.And)
.Search(x => x.Fields, searchParts, 1, SearchOptions.Or, @operator: SearchOperator.And)
.Paging(1, 20)
.ProjectInto<ZeroEntity>()
.ToListAsync();
return new ListResult<ZeroEntity>(results, stats.TotalResults, 1, 20);
List<SearchResult> items = new();
foreach (ZeroEntity result in results)
{
items.Add(new()
{
Id = result.Id,
Name = result.Name,
IsActive = result.IsActive,
Url = "/"
});
}
return new ListResult<SearchResult>(items, stats.TotalResults, 1, 20);
}
}
public interface IBackofficeSearchService
{
Task<ListResult<ZeroEntity>> Query(string searchTerm);
Task<ListResult<SearchResult>> Query(string searchTerm);
}
}
+1 -1
View File
@@ -159,7 +159,7 @@ namespace zero.Web
// create all indexes
if (options.SetupCompleted)
{
var indexes = options.Raven.Indexes.GetAllForRegistration(options);
var indexes = options.Raven.Indexes.BuildAll(options, store);
IndexCreation.CreateIndexes(indexes, store, database: options.Raven.Database);
}