You've already forked multi-select-facet
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:
123
cmd/api/main.go
123
cmd/api/main.go
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
3
go.mod
@@ -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
8
go.sum
@@ -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=
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user