Skip to content

Added tags aggregation in SearchController for .NET Core example #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 6.x-codecomplete-netcore
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 106 additions & 64 deletions src/NuSearch.Web/Controllers/SearchController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Nest;
using NuSearch.Domain.Model;
Expand All @@ -14,84 +16,124 @@ public class SearchController : Controller
public SearchController(IElasticClient client) => _client = client;

[HttpGet]
public IActionResult Index(SearchForm form)
public async Task<IActionResult> Index(SearchForm form)
{
var result = _client.Search<Package>(s => s
var response = await _client.SearchAsync<Package>(s => s
.From((form.Page - 1) * form.PageSize)
.Size(form.PageSize)
.Sort(sort =>
{
if (form.Sort == SearchSort.Downloads)
return sort.Descending(p => p.DownloadCount);
if (form.Sort == SearchSort.Recent)
return sort.Field(sortField => sortField
.Nested(n => n
.Path(p => p.Versions)
)
.Field(p => p.Versions.First().LastUpdated)
.Descending()
);
return sort.Descending(SortSpecialField.Score);
})
.Aggregations(a => a
.Nested("authors", n => n
.Path(p => p.Authors)
.Aggregations(aa => aa
.Terms("author-names", ts => ts
.Field(p => p.Authors.First().Name.Suffix("raw"))
)
)
)
)
.Query(q => (q
.Match(m => m
.Field(p => p.Id.Suffix("keyword"))
.Boost(1000)
.Query(form.Query)
) || q
.FunctionScore(fs => fs
.MaxBoost(50)
.Functions(ff => ff
.FieldValueFactor(fvf => fvf
.Field(p => p.DownloadCount)
.Factor(0.0001)
)
)
.Query(query => query
.MultiMatch(m => m
.Fields(f => f
.Field(p => p.Id, 1.5)
.Field(p => p.Summary, 0.8)
)
.Operator(Operator.And)
.Query(form.Query)
)
)
))
&& +q.Nested(n => n
.Path(p => p.Authors)
.Query(nq => +nq
.Term(p => p.Authors.First().Name.Suffix("raw"), form.Author)
)
)
)
.Sort(sort => ApplySort(sort, form))
.Aggregations(aggs => ApplyAggregations(aggs, form))
.Query(q => ApplyQuery(form, q))
);

var authors = result.Aggregations.Nested("authors")
if (response == null) throw new Exception("elastic response is null");

var authors = response.Aggregations.Nested("authors")
.Terms("author-names")
.Buckets
.ToDictionary(k => k.Key, v => v.DocCount);

Dictionary<string, long> tags;
if (form.Significance)
tags = response.Aggregations.SignificantTerms("tags")
.Buckets
.ToDictionary(k => k.Key, v => v.DocCount);
else
tags = response.Aggregations.Terms("tags")
.Buckets
.ToDictionary(k => k.Key, v => v.DocCount ?? 0);

var model = new SearchViewModel
{
Hits = result.Hits,
Total = result.Total,
Hits = response.Hits,
Total = response.Total,
Form = form,
TotalPages = (int)Math.Ceiling(result.Total / (double)form.PageSize),
Authors = authors
TotalPages = (int)Math.Ceiling(response.Total / (double)form.PageSize),
Authors = authors,
Tags = tags
};

return View(model);
}
}

private static AggregationContainerDescriptor<Package> ApplyAggregations(AggregationContainerDescriptor<Package> aggs, SearchForm form)
{
aggs = form.Significance
? aggs.SignificantTerms("tags", t => t.Field(p => p.Tags))
: aggs.Terms("tags", t => t.Field(p => p.Tags));

return aggs.Nested("authors", n => n
.Path(p => p.Authors)
.Aggregations(aa => aa
.Terms("author-names", ts => ts
.Field(p => p.Authors.First().Name.Suffix("raw"))
)
)
);
}

private static SortDescriptor<Package> ApplySort(SortDescriptor<Package> sort, SearchForm form)
{
if (form.Sort == SearchSort.Downloads)
return sort.Descending(p => p.DownloadCount);
if (form.Sort == SearchSort.Recent)
return sort.Field(sortField => sortField
.Nested(n => n
.Path(p => p.Versions)
)
.Field(p => p.Versions.First().LastUpdated)
.Descending()
);

return sort.Descending(SortSpecialField.Score);
}

private static QueryContainer ApplyQuery(SearchForm form, QueryContainerDescriptor<Package> q)
{
return (ExactIdKeywordMatch(form, q) || QueryWithRelevancyTunedBasedOnDownloadCount(form, q))
&& FilterAuthorSelection(form, q)
&& FilterTagSelection(form, q);
}

private static QueryContainer FilterAuthorSelection(SearchForm form, QueryContainerDescriptor<Package> q) => +q
.Nested(n => n
.Path(p => p.Authors)
.Query(nq => +nq
.Term(p => p.Authors.First().Name.Suffix("raw"), form.Author)
)
);

private static QueryContainer QueryWithRelevancyTunedBasedOnDownloadCount(SearchForm form, QueryContainerDescriptor<Package> q) => q
.FunctionScore(fs => fs
.MaxBoost(10)
.Functions(ff => ff
.FieldValueFactor(fvf => fvf
.Field(p => p.DownloadCount)
.Factor(0.0001)
)
)
.Query(query => query
.MultiMatch(m => m
.Fields(f => f
.Field(p => p.Id.Suffix("keyword"), 1.5)
.Field(p => p.Id, 1.5)
.Field(p => p.Summary, 0.8)
)
.Operator(Operator.And)
.Query(form.Query)
)
)
);

private static QueryContainer ExactIdKeywordMatch(SearchForm form, QueryContainerDescriptor<Package> q) => q
.Match(m => m
.Field(p => p.Id.Suffix("keyword"))
.Boost(1000)
.Query(form.Query)
);

private static QueryContainer FilterTagSelection(
SearchForm form,
QueryContainerDescriptor<Package> q) => form.Tags.Aggregate(new QueryContainer(), (c, s) => c && +q.Term(p => p.Tags, s), c => c);
}
}