Skip to content

Commit 3068cf8

Browse files
authored
Merge pull request #3253 from SeedCompany/full-text-filters
2 parents e270efb + caedbf1 commit 3068cf8

File tree

12 files changed

+222
-6
lines changed

12 files changed

+222
-6
lines changed

src/components/engagement/dto/list-engagements.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export abstract class EngagementFilters {
2424
})
2525
readonly type?: 'language' | 'internship';
2626

27+
@Field({
28+
nullable: true,
29+
})
30+
readonly name?: string;
31+
2732
@Field(() => [EngagementStatus], {
2833
nullable: true,
2934
})

src/components/engagement/engagement.repository.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
createRelationships,
3333
defineSorters,
3434
filter,
35+
FullTextIndex,
3536
INACTIVE,
3637
matchChangesetAndChangedProps,
3738
matchProjectSens,
@@ -514,6 +515,12 @@ export class EngagementRepository extends CommonRepository {
514515
private createIndexes() {
515516
return this.getConstraintsFor(IEngagement);
516517
}
518+
@OnIndex('schema')
519+
private async createSchemaIndexes() {
520+
await this.db.query().apply(NameIndex.create()).run();
521+
await this.db.query().apply(LanguageNameIndex.create()).run();
522+
await this.db.query().apply(InternshipNameIndex.create()).run();
523+
}
517524
}
518525

519526
export const engagementFilters = filter.define(() => EngagementFilters, {
@@ -526,6 +533,25 @@ export const engagementFilters = filter.define(() => EngagementFilters, {
526533
),
527534
}),
528535
status: filter.stringListProp(),
536+
name: filter.fullText({
537+
index: () => NameIndex,
538+
matchToNode: (q) =>
539+
q.match([
540+
node('node', 'Engagement'),
541+
relation('either', '', undefined, ACTIVE),
542+
node('', 'BaseNode'),
543+
relation('out', '', undefined, ACTIVE),
544+
node('match'),
545+
]),
546+
// UI joins project & language/intern names with dash
547+
// Remove it from search if users type it
548+
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,
554+
}),
529555
projectId: filter.pathExists((id) => [
530556
node('node'),
531557
relation('in', '', 'engagement'),
@@ -658,3 +684,22 @@ const multiPropsAsSortString = (props: string[]) =>
658684
' + ',
659685
props.map((prop) => `coalesce(${prop}.value, "")`),
660686
) + ' as sortValue';
687+
688+
const NameIndex = FullTextIndex({
689+
indexName: 'EngagementName',
690+
labels: ['ProjectName', 'LanguageName', 'LanguageDisplayName', 'UserName'],
691+
properties: 'value',
692+
analyzer: 'standard-folding',
693+
});
694+
const LanguageNameIndex = FullTextIndex({
695+
indexName: 'LanguageEngagementName',
696+
labels: ['ProjectName', 'LanguageName', 'LanguageDisplayName'],
697+
properties: 'value',
698+
analyzer: 'standard-folding',
699+
});
700+
const InternshipNameIndex = FullTextIndex({
701+
indexName: 'InternshipEngagementName',
702+
labels: ['ProjectName', 'UserName'],
703+
properties: 'value',
704+
analyzer: 'standard-folding',
705+
});

src/components/language/dto/list-language.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export abstract class EthnologueLanguageFilters {
3030

3131
@InputType()
3232
export abstract class LanguageFilters {
33+
@Field({
34+
nullable: true,
35+
})
36+
readonly name?: string;
37+
3338
@Field(() => [Sensitivity], {
3439
description: 'Only languages with these sensitivities',
3540
nullable: true,

src/components/language/language.repository.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
Session,
1818
UnsecuredDto,
1919
} from '~/common';
20-
import { DtoRepository, UniquenessError } from '~/core/database';
20+
import { DtoRepository, OnIndex, UniquenessError } from '~/core/database';
2121
import {
2222
ACTIVE,
2323
any,
@@ -27,6 +27,7 @@ import {
2727
defineSorters,
2828
exp,
2929
filter,
30+
FullTextIndex,
3031
matchChangesetAndChangedProps,
3132
matchProjectScopedRoles,
3233
matchProjectSens,
@@ -268,9 +269,23 @@ export class LanguageRepository extends DtoRepository<
268269
.first();
269270
return !!res;
270271
}
272+
273+
@OnIndex('schema')
274+
private async createSchemaIndexes() {
275+
await this.db.query().apply(NameIndex.create()).run();
276+
}
271277
}
272278

273279
export const languageFilters = filter.define(() => LanguageFilters, {
280+
name: filter.fullText({
281+
index: () => NameIndex,
282+
matchToNode: (q) =>
283+
q.match([
284+
node('node', 'Language'),
285+
relation('out', '', undefined, ACTIVE),
286+
node('match'),
287+
]),
288+
}),
274289
sensitivity: filter.stringListProp(),
275290
leastOfThese: filter.propVal(),
276291
isSignLanguage: filter.propVal(),
@@ -355,3 +370,10 @@ export const languageSorters = defineSorters(Language, {
355370
});
356371

357372
const ethnologueSorters = defineSorters(EthnologueLanguage, {});
373+
374+
const NameIndex = FullTextIndex({
375+
indexName: 'LanguageName',
376+
labels: ['LanguageName', 'LanguageDisplayName'],
377+
properties: 'value',
378+
analyzer: 'standard-folding',
379+
});

src/components/project/dto/list-projects.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import {
2323

2424
@InputType()
2525
export abstract class ProjectFilters {
26+
@Field({
27+
nullable: true,
28+
})
29+
readonly name?: string;
30+
2631
@Field(() => [ProjectType], {
2732
description: 'Only projects of these types',
2833
nullable: true,

src/components/project/project-filters.query.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import { greaterThan, inArray, node, relation } from 'cypher-query-builder';
22
import { ACTIVE, filter, matchProjectSens, path } from '~/core/database/query';
33
import { ProjectFilters } from './dto';
4+
import { ProjectNameIndex } from './project.repository';
45

56
export const projectFilters = filter.define(() => ProjectFilters, {
7+
name: filter.fullText({
8+
index: () => ProjectNameIndex,
9+
matchToNode: (q) =>
10+
q.match([
11+
node('node', 'Project'),
12+
relation('out', '', 'name', ACTIVE),
13+
node('match'),
14+
]),
15+
minScore: 0.8,
16+
}),
617
type: filter.stringListBaseNodeProp(),
718
status: filter.stringListProp(),
819
onlyMultipleEngagements:

src/components/project/project.repository.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
createNode,
1818
createRelationships,
1919
defineSorters,
20+
FullTextIndex,
2021
matchChangesetAndChangedProps,
2122
matchProjectSens,
2223
matchPropsAndProjectSensAndScopedRoles,
@@ -323,8 +324,19 @@ export class ProjectRepository extends CommonRepository {
323324
private createIndexes() {
324325
return this.getConstraintsFor(IProject);
325326
}
327+
@OnIndex('schema')
328+
private async createSchemaIndexes() {
329+
await this.db.query().apply(ProjectNameIndex.create()).run();
330+
}
326331
}
327332

333+
export const ProjectNameIndex = FullTextIndex({
334+
indexName: 'ProjectName',
335+
labels: 'ProjectName',
336+
properties: 'value',
337+
analyzer: 'standard-folding',
338+
});
339+
328340
export const projectSorters = defineSorters(IProject, {
329341
sensitivity: (query) =>
330342
query

src/components/user/dto/user.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,19 @@ export class User extends Interfaces {
5252
email: SecuredStringNullable;
5353

5454
@NameField()
55+
@DbLabel('UserName')
5556
realFirstName: SecuredString;
5657

5758
@NameField()
59+
@DbLabel('UserName')
5860
realLastName: SecuredString;
5961

6062
@NameField()
63+
@DbLabel('UserName')
6164
displayFirstName: SecuredString;
6265

6366
@NameField()
67+
@DbLabel('UserName')
6468
displayLastName: SecuredString;
6569

6670
@Field()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { BaseMigration, Migration } from '~/core/database';
2+
3+
@Migration('2024-06-21T09:00:00')
4+
export class AddUserNameLabelMigration extends BaseMigration {
5+
async up() {
6+
await this.db.query().raw`
7+
call {
8+
match ()-[:realFirstName { active: true }]->(node:Property) return node
9+
union
10+
match ()-[:realLastName { active: true }]->(node:Property) return node
11+
union
12+
match ()-[:displayFirstName { active: true }]->(node:Property) return node
13+
union
14+
match ()-[:displayLastName { active: true }]->(node:Property) return node
15+
}
16+
set node:UserName
17+
`.executeAndLogStats();
18+
}
19+
}

src/components/user/user.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { EducationModule } from './education/education.module';
1313
import { KnownLanguageRepository } from './known-language.repository';
1414
import { KnownLanguageResolver } from './known-language.resolver';
1515
import { AddActorLabelMigration } from './migrations/add-actor-label.migration';
16+
import { AddUserNameLabelMigration } from './migrations/add-user-name-label.migration';
1617
import { SystemAgentEdgeDBRepository } from './system-agent.edgedb.repository';
1718
import { SystemAgentNeo4jRepository } from './system-agent.neo4j.repository';
1819
import { SystemAgentRepository } from './system-agent.repository';
@@ -49,6 +50,7 @@ import { UserService } from './user.service';
4950
provide: SystemAgentRepository,
5051
},
5152
AddActorLabelMigration,
53+
AddUserNameLabelMigration,
5254
],
5355
exports: [
5456
UserService,

0 commit comments

Comments
 (0)