1
0
mirror of https://github.com/stevenferrer/multi-select-facet.git synced 2025-11-23 21:54:45 +02:00

improve search and auto-suggest

- include query term in search
- improved autosuggest
This commit is contained in:
Steven Ferrer
2020-06-30 15:52:46 +08:00
parent 48c79f4be8
commit 8664f33a10
7 changed files with 127 additions and 76 deletions

View File

@@ -100,76 +100,76 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie
// auto-suggest field type
fieldTypes := []solrschema.FieldType{
// // approach #1
// // see: https://blog.griddynamics.com/implementing-autocomplete-with-solr/
// {
// Name: "text_suggest",
// Class: "solr.TextField",
// PositionIncrementGap: "100",
// IndexAnalyzer: &solrschema.Analyzer{
// Tokenizer: &solrschema.Tokenizer{
// Class: "solr.StandardTokenizerFactory",
// },
// Filters: []solrschema.Filter{
// {
// Class: "solr.LowerCaseFilterFactory",
// },
// {
// Class: "solr.EdgeNGramFilterFactory",
// MinGramSize: 1,
// MaxGramSize: 100,
// },
// },
// },
// QueryAnalyzer: &solrschema.Analyzer{
// Tokenizer: &solrschema.Tokenizer{
// Class: "solr.KeywordTokenizerFactory",
// },
// Filters: []solrschema.Filter{
// {
// Class: "solr.LowerCaseFilterFactory",
// },
// },
// },
// },
// approach #2
// see: https://blog.griddynamics.com/implement-autocomplete-search-for-large-e-commerce-catalogs/
// approach #1
// see: https://blog.griddynamics.com/implementing-autocomplete-with-solr/
{
Name: "text_suggest",
Class: "solr.TextField",
Stored: true,
Name: "text_suggest",
Class: "solr.TextField",
PositionIncrementGap: "100",
IndexAnalyzer: &solrschema.Analyzer{
Tokenizer: &solrschema.Tokenizer{
Class: "solr.WhitespaceTokenizerFactory",
Class: "solr.StandardTokenizerFactory",
},
Filters: []solrschema.Filter{
{
Class: "solr.LowerCaseFilterFactory",
},
{
Class: "solr.ASCIIFoldingFilterFactory",
Class: "solr.EdgeNGramFilterFactory",
MinGramSize: 1,
MaxGramSize: 100,
},
},
},
QueryAnalyzer: &solrschema.Analyzer{
Tokenizer: &solrschema.Tokenizer{
Class: "solr.WhitespaceTokenizerFactory",
Class: "solr.KeywordTokenizerFactory",
},
Filters: []solrschema.Filter{
{
Class: "solr.LowerCaseFilterFactory",
},
{
Class: "solr.ASCIIFoldingFilterFactory",
},
{
Class: "solr.SynonymGraphFilterFactory",
Synonyms: "synonyms.txt",
},
},
},
},
// // approach #2
// // see: https://blog.griddynamics.com/implement-autocomplete-search-for-large-e-commerce-catalogs/
// {
// Name: "text_suggest",
// Class: "solr.TextField",
// Stored: true,
// IndexAnalyzer: &solrschema.Analyzer{
// Tokenizer: &solrschema.Tokenizer{
// Class: "solr.WhitespaceTokenizerFactory",
// },
// Filters: []solrschema.Filter{
// {
// Class: "solr.LowerCaseFilterFactory",
// },
// {
// Class: "solr.ASCIIFoldingFilterFactory",
// },
// },
// },
// QueryAnalyzer: &solrschema.Analyzer{
// Tokenizer: &solrschema.Tokenizer{
// Class: "solr.WhitespaceTokenizerFactory",
// },
// Filters: []solrschema.Filter{
// {
// Class: "solr.LowerCaseFilterFactory",
// },
// {
// Class: "solr.ASCIIFoldingFilterFactory",
// },
// {
// Class: "solr.SynonymGraphFilterFactory",
// Synonyms: "synonyms.txt",
// },
// },
// },
// },
}
for _, fieldType := range fieldTypes {
@@ -247,6 +247,27 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie
Source: "*_s",
Dest: "suggest",
},
{
Source: "name",
Dest: "_text_",
},
{
Source: "category",
Dest: "_text_",
},
{
Source: "brand",
Dest: "_text_",
},
{
Source: "productType",
Dest: "_text_",
},
{
Source: "*_s",
Dest: "_text_",
},
}
for _, copyField := range copyFields {
@@ -267,7 +288,7 @@ func initSuggestConfig(ctx context.Context, collection string, configClient solr
"name": "suggest",
"class": "solr.SuggestComponent",
"suggester": map[string]string{
"name": "mySuggester",
"name": "default",
"lookupImpl": "FuzzyLookupFactory",
"dictionaryImpl": "DocumentDictionaryFactory",
"field": "suggest",
@@ -285,7 +306,7 @@ func initSuggestConfig(ctx context.Context, collection string, configClient solr
"defaults": map[string]Any{
"suggest": true,
"suggest.count": 10,
"suggest.dictionary": "mySuggester",
"suggest.dictionary": "default",
},
"components": []string{"suggest"},
},
@@ -301,7 +322,7 @@ func initSuggestConfig(ctx context.Context, collection string, configClient solr
}
func indexProducts(ctx context.Context, collection string,
indexClient solrindex.JSONClient) error {
indexClient solrindex.Client) error {
b, err := ioutil.ReadFile(dataPath)
if err != nil {
return err

View File

@@ -128,6 +128,15 @@ func (h *searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
q := r.URL.Query().Get("q")
if len(q) == 0 {
q = "*"
} else {
q = fmt.Sprintf("%q", q)
}
productFilters = append(productFilters, fmt.Sprintf("{!tag=top}_text_:%s", q))
query := Map{
"query": "{!parent tag=top filters=$skuFilters which=$product score=total v=$sku}",
"queries": Map{

View File

@@ -20,15 +20,9 @@ func (h *suggestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
dict := "mySuggester"
suggestResp, err := h.solrClient.Suggester().
Suggest(r.Context(), suggester.Request{
Collection: h.collection,
Params: suggester.Params{
Query: q,
Dictionaries: []string{dict},
},
})
dict := "default"
suggestResp, err := h.solrClient.Suggester().Suggest(r.Context(), h.collection,
suggester.Params{Query: q, Dictionaries: []string{dict}})
if err != nil {
http.Error(w, err.Error(), 500)
return

3
go.mod
View File

@@ -3,9 +3,10 @@ module github.com/stevenferrer/multi-select-facet
go 1.14
require (
github.com/davecgh/go-spew v1.1.0
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.1.1
github.com/pkg/errors v0.9.1
github.com/stevenferrer/solr-go v0.1.1
github.com/stevenferrer/solr-go v0.1.2
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
)

8
go.sum
View File

@@ -10,12 +10,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stevenferrer/solr-go v0.0.9 h1:G8PAQAWjbtoVNS0DOQ3g+7Bdp/nUgFCiyqoRsh56aeo=
github.com/stevenferrer/solr-go v0.0.9/go.mod h1:ePa8+6kV1baPpoywPbmcR06wQ0500EGuMntYGxli5Xk=
github.com/stevenferrer/solr-go v0.1.0 h1:gCw5J4szWcExmLwg+JMJjgs2wZI7x6Bgd5hhNoP3rjs=
github.com/stevenferrer/solr-go v0.1.0/go.mod h1:ePa8+6kV1baPpoywPbmcR06wQ0500EGuMntYGxli5Xk=
github.com/stevenferrer/solr-go v0.1.1 h1:xqGrvW9p0q4oVNgH0lj+pImEOISClgdnD+DVjn+RUxw=
github.com/stevenferrer/solr-go v0.1.1/go.mod h1:ePa8+6kV1baPpoywPbmcR06wQ0500EGuMntYGxli5Xk=
github.com/stevenferrer/solr-go v0.1.2 h1:IT57+MJ8gUtl4Iu5E9+EK7pg6+bX0MOn0dz7bSYPeaY=
github.com/stevenferrer/solr-go v0.1.2/go.mod h1:ePa8+6kV1baPpoywPbmcR06wQ0500EGuMntYGxli5Xk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@@ -6,8 +6,11 @@
<Filters :facets="facets" @changed="onChanged" />
</div>
<div class="column">
<Search />
<Search @selected="onSelected" />
<br />
{{ query }}
<br />
<div class="columns is-multiline is-mobile">
<div
v-for="(product, i) in products"
@@ -27,12 +30,14 @@ import Filters from "./components/Filters";
import Product from "./components/Product";
import Search from "./components/Search";
async function search(filters) {
async function search(query, filters) {
if (!filters) {
filters = [];
}
const url = new URL("http://localhost:8081/search");
const url = new URL(
`http://localhost:8081/search?q=${encodeURIComponent(query)}`
);
filters.forEach((filter) => {
url.searchParams.append(filter.param, filter.selected.join(","));
});
@@ -87,12 +92,13 @@ export default {
},
data() {
return {
query: "",
products: null,
facets: null,
};
},
async mounted() {
const { products, facets } = await search();
const { products, facets } = await search(this.query);
this.products = products;
this.facets = facets;
},
@@ -105,7 +111,25 @@ export default {
return { name, param, selected };
});
const { products, facets } = await search(filters);
const { products, facets } = await search(this.query, filters);
this.products = products;
this.facets = facets;
},
async onSelected(option) {
if (!option) return;
const { term: query } = option;
this.query = query;
const { facets: oldFacet } = this;
const filters = oldFacet
.filter(({ selected }) => selected.length > 0)
.map(({ name, param, selected }) => {
return { name, param, selected };
});
const { products, facets } = await search(query, filters);
this.products = products;
this.facets = facets;
},

View File

@@ -8,7 +8,8 @@
icon="search"
:loading="isFetching"
@typing="getAsyncData"
@select="(option) => (selected = option)"
@select="onSelected"
keep-first
clearable
>
<template slot-scope="props">
@@ -31,15 +32,15 @@ export default {
};
},
methods: {
getAsyncData: debounce(function(name) {
if (!name.length) {
getAsyncData: debounce(function(query) {
if (!query.length) {
this.data = [];
return;
}
this.isFetching = true;
this.$http
.get(`http://localhost:8081/suggest?q=${encodeURIComponent(name)}`)
.get(`http://localhost:8081/suggest?q=${encodeURIComponent(query)}`)
.then(({ data }) => {
this.data = [];
data.suggestions.forEach((item) => this.data.push(item));
@@ -52,6 +53,11 @@ export default {
this.isFetching = false;
});
}, 500),
onSelected(option) {
this.selected = option;
this.$emit("selected", option);
},
},
};
</script>