Skip to content

Commit caedbf1

Browse files
committed
For engagements name filter, search each word individually
Since the search phrase could be represented over multiple nodes, instead of each node containing the whole phase. This would apply to user's name filter as well
1 parent 31f0690 commit caedbf1

File tree

3 files changed

+39
-18
lines changed

3 files changed

+39
-18
lines changed

src/components/engagement/engagement.repository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ export const engagementFilters = filter.define(() => EngagementFilters, {
546546
// UI joins project & language/intern names with dash
547547
// Remove it from search if users type it
548548
normalizeInput: (v) => v.replaceAll(/ -/g, ''),
549+
// Treat each word as a separate search term
550+
// Each word could point to a different node
551+
// i.e. "project - language"
552+
separateQueryForEachWord: true,
553+
minScore: 0.9,
549554
}),
550555
projectId: filter.pathExists((id) => [
551556
node('node'),

src/core/database/query/filters.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Comparator } from 'cypher-query-builder/dist/typings/clauses/where-comp
1515
import { identity, isFunction } from 'lodash';
1616
import { AbstractClass, ConditionalKeys } from 'type-fest';
1717
import { DateTimeFilter } from '~/common';
18+
import { variable } from '../query-augmentation/condition-variables';
1819
import { collect } from './cypher-functions';
1920
import { escapeLuceneSyntax, FullTextIndex } from './full-text';
2021
import { ACTIVE } from './matching';
@@ -240,13 +241,15 @@ export const fullText =
240241
({
241242
index,
242243
normalizeInput,
244+
separateQueryForEachWord,
243245
escapeLucene = true,
244246
toLucene,
245247
minScore = 0,
246248
matchToNode,
247249
}: {
248250
index: () => FullTextIndex;
249251
normalizeInput?: (input: string) => string;
252+
separateQueryForEachWord?: boolean;
250253
escapeLucene?: boolean;
251254
toLucene?: (input: string) => string;
252255
minScore?: number;
@@ -261,16 +264,20 @@ export const fullText =
261264
return null;
262265
}
263266

264-
let input: string = value;
267+
const normalized = normalizeInput
268+
? normalizeInput(value as string)
269+
: (value as string);
265270

266-
input = normalizeInput ? normalizeInput(input) : input;
267-
input = escapeLucene ? escapeLuceneSyntax(input) : input;
271+
let input = separateQueryForEachWord
272+
? cleanSplit(normalized, ' ')
273+
: [normalized];
268274

269-
const lucene =
270-
toLucene?.(input) ??
275+
input = escapeLucene ? input.map(escapeLuceneSyntax) : input;
276+
277+
toLucene ??= (query) =>
271278
// Default to each word being matched.
272279
// And for each word...
273-
cleanSplit(input, ' ')
280+
cleanSplit(query, ' ')
274281
.map((term) => {
275282
const adjusted = [
276283
// fuzzy (distance) search with boosted priority
@@ -285,16 +292,24 @@ export const fullText =
285292
query
286293
.subQuery((q) =>
287294
q
288-
.call(
289-
index()
290-
.search(lucene, { limit: 100 })
291-
.yield({ node: 'match', score: true }),
295+
.unwind(input.map(toLucene!), 'query')
296+
.subQuery('query', (qq) =>
297+
qq
298+
.call(
299+
index()
300+
.search(variable('query'), { limit: 100 })
301+
.yield({ node: 'match', score: true }),
302+
)
303+
.where({ score: greaterThan(minScore) })
304+
.apply(matchToNode)
305+
.return(collect('distinct node').as(`${field}Matches`)),
292306
)
293-
.where({ score: greaterThan(minScore) })
294-
.apply(matchToNode)
295-
.return(collect('distinct node').as(`${field}Matches`)),
307+
.return(collect(`${field}Matches`).as(`${field}MatchSets`)),
296308
)
297309
.with('*');
298310

299-
return { node: inArray(`${field}Matches`, true) };
311+
return {
312+
node: () =>
313+
`all(${`${field}Matches`} in ${`${field}MatchSets`} where node in ${`${field}Matches`})`,
314+
};
300315
};

src/core/database/query/full-text.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Query } from 'cypher-query-builder';
33
import { pickBy } from 'lodash';
44
import { LiteralUnion } from 'type-fest';
55
import { procedure } from '../query-augmentation/call';
6+
import { Variable } from '../query-augmentation/condition-variables';
67
import { CypherExpression, exp, isExp } from './cypher-expression';
78
import { db } from './cypher-functions';
89

@@ -63,7 +64,7 @@ export const FullTextIndex = (config: {
6364
* @see https://lucene.apache.org/core/2_9_4/queryparsersyntax.html
6465
*/
6566
search: (
66-
query: string,
67+
query: string | Variable,
6768
options: {
6869
skip?: number;
6970
limit?: number;
@@ -72,7 +73,7 @@ export const FullTextIndex = (config: {
7273
) => {
7374
// fallback to "" when no query is given, so that no results are
7475
// returned instead of the procedure failing
75-
query = query.trim() || '""';
76+
query = typeof query === 'string' ? query.trim() || '""' : query;
7677

7778
return db.index.fulltext.queryNodes(indexName, query, options);
7879
},
@@ -85,8 +86,8 @@ export const escapeLuceneSyntax = (query: string) =>
8586
.replace(/\b(OR|AND|NOT)\b/g, (char) => `"${char}"`);
8687

8788
export const IndexFullTextQueryNodes = (
88-
indexName: string,
89-
query: string,
89+
indexName: string | Variable,
90+
query: string | Variable,
9091
options?:
9192
| {
9293
skip?: number;

0 commit comments

Comments
 (0)