@@ -10,6 +10,11 @@ import util from '../../util';
1010
1111const ES_PROJECT_INDEX = config . get ( 'elasticsearchConfig.indexName' ) ;
1212const ES_PROJECT_TYPE = config . get ( 'elasticsearchConfig.docType' ) ;
13+
14+ const MATCH_TYPE_EXACT_PHRASE = 1 ;
15+ const MATCH_TYPE_WILDCARD = 2 ;
16+ const MATCH_TYPE_SINGLE_FIELD = 3 ;
17+
1318/**
1419 * API to handle retrieving projects
1520 *
@@ -40,6 +45,59 @@ const PROJECT_PHASE_PRODUCTS_ATTRIBUTES = _.without(
4045
4146
4247const escapeEsKeyword = keyword => keyword . replace ( / [ + - = > < ! | ( ) { } [ & \] ^ " ~ * ? : \\ / ] / g, '\\\\$&' ) ;
48+
49+ const buildEsFullTextQuery = ( keyword , matchType , singleFieldName ) => {
50+ let should = [
51+ {
52+ query_string : {
53+ query : ( matchType === MATCH_TYPE_EXACT_PHRASE ) ? keyword : `*${ keyword } *` ,
54+ analyze_wildcard : ( matchType === MATCH_TYPE_WILDCARD ) ,
55+ fields : [ 'name^5' , 'description^3' , 'type^2' ] ,
56+ } ,
57+ } ,
58+ {
59+ nested : {
60+ path : 'details' ,
61+ query : {
62+ nested : {
63+ path : 'details.utm' ,
64+ query : {
65+ query_string : {
66+ query : ( matchType === MATCH_TYPE_EXACT_PHRASE ) ? keyword : `*${ keyword } *` ,
67+ analyze_wildcard : ( matchType === MATCH_TYPE_WILDCARD ) ,
68+ fields : [ 'details.utm.code^4' ] ,
69+ } ,
70+ } ,
71+ } ,
72+ } ,
73+ } ,
74+ } ,
75+ {
76+ nested : {
77+ path : 'members' ,
78+ query : {
79+ query_string : {
80+ query : ( matchType === MATCH_TYPE_EXACT_PHRASE ) ? keyword : `*${ keyword } *` ,
81+ analyze_wildcard : ( matchType === MATCH_TYPE_WILDCARD ) ,
82+ fields : [ 'members.email' , 'members.handle' , 'members.firstName' , 'members.lastName' ] ,
83+ } ,
84+ } ,
85+ } ,
86+ } ,
87+ ] ;
88+
89+ if ( matchType === MATCH_TYPE_SINGLE_FIELD && singleFieldName === 'ref' ) {
90+ // only need to match the second item in the should array
91+ should = should . slice ( 1 , 2 ) ;
92+ }
93+
94+ return {
95+ bool : {
96+ should,
97+ } ,
98+ } ;
99+ } ;
100+
43101/**
44102 * Parse the ES search criteria and prepare search request body
45103 *
@@ -126,41 +184,50 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
126184 if ( _ . has ( criteria , 'filters.keyword' ) ) {
127185 // keyword is a full text search
128186 // escape special fields from keyword search
129- const keyword = escapeEsKeyword ( criteria . filters . keyword ) ;
130- fullTextQuery = {
131- bool : {
132- should : [
133- {
134- query_string : {
135- query : `*${ keyword } *` ,
136- analyze_wildcard : true ,
137- fields : [ 'name^3' , 'description' , 'type' ] , // boost name field
138- } ,
139- } ,
140- {
141- nested : {
142- path : 'members' ,
143- query : {
144- query_string : {
145- query : `*${ keyword } *` ,
146- analyze_wildcard : true ,
147- fields : [ 'members.email' , 'members.handle' , 'members.firstName' , 'members.lastName' ] ,
148- } ,
149- } ,
150- } ,
151- } ,
152- ] ,
153- } ,
154- } ;
187+ const keywordCriterion = criteria . filters . keyword ;
188+ let keyword ;
189+ let matchType ;
190+ let singleFieldName ;
191+ // check exact phrase match first (starts and ends with double quotes)
192+ if ( keywordCriterion . startsWith ( '"' ) && keywordCriterion . endsWith ( '"' ) ) {
193+ keyword = keywordCriterion ;
194+ matchType = MATCH_TYPE_EXACT_PHRASE ;
195+ }
196+
197+ if ( keywordCriterion . indexOf ( ' ' ) > - 1 || keywordCriterion . indexOf ( ':' ) > - 1 ) {
198+ const parts = keywordCriterion . split ( ' ' ) ;
199+ if ( parts . length > 0 ) {
200+ for ( let i = 0 ; i < parts . length ; i += 1 ) {
201+ const part = parts [ i ] . trim ( ) ;
202+ if ( part . length > 4 && part . startsWith ( 'ref:' ) ) {
203+ keyword = escapeEsKeyword ( part . substring ( 4 ) ) ;
204+ matchType = MATCH_TYPE_SINGLE_FIELD ;
205+ singleFieldName = part . substring ( 0 , 3 ) ;
206+ break ;
207+ }
208+ }
209+ }
210+ }
211+
212+ if ( ! keyword ) {
213+ // Not a specific field search nor an exact phrase search, do a wildcard match
214+ keyword = escapeEsKeyword ( criteria . filters . keyword ) ;
215+ matchType = MATCH_TYPE_WILDCARD ;
216+ }
217+
218+ fullTextQuery = buildEsFullTextQuery ( keyword , matchType , singleFieldName ) ;
155219 }
156220 const body = { query : { } } ;
157221 if ( boolQuery . length > 0 ) {
158222 body . query . bool = {
159- must : boolQuery ,
223+ filter : boolQuery ,
160224 } ;
161225 }
162226 if ( fullTextQuery ) {
163227 body . query = _ . merge ( body . query , fullTextQuery ) ;
228+ if ( body . query . bool ) {
229+ body . query . bool . minimum_should_match = 1 ;
230+ }
164231 }
165232
166233 if ( fullTextQuery || boolQuery . length > 0 ) {
0 commit comments