Skip to content

Commit cb0f94b

Browse files
fix(search): include attachment fields in message search
1 parent 4235cd9 commit cb0f94b

File tree

2 files changed

+57
-139
lines changed

2 files changed

+57
-139
lines changed

apps/meteor/server/lib/parseMessageSearchQuery.ts

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -241,37 +241,44 @@ class MessageSearchQueryParser {
241241
/**
242242
* Query in message text
243243
*/
244-
private consumeMessageText(text: string) {
245-
text = text.trim().replace(/\s\s/g, ' ');
246-
247-
if (text === '') {
248-
return text;
249-
}
250-
251-
if (/^\/.+\/[imxs]*$/.test(text)) {
252-
const r = text.split('/');
253-
this.query.msg = {
254-
$regex: r[1],
255-
$options: r[2],
256-
};
257-
} else if (this.forceRegex) {
258-
this.query.msg = {
259-
$regex: text,
260-
$options: 'i',
261-
};
262-
} else {
263-
this.query.$text = {
264-
$search: text,
265-
};
266-
this.options.projection = {
267-
score: {
268-
$meta: 'textScore',
269-
},
270-
};
271-
}
244+
/**
245+
* Query in message text + attachments
246+
*/
247+
private consumeMessageText(text: string) {
248+
text = text.trim().replace(/\s\s+/g, ' ');
249+
250+
if (!text || text.length < 2) {
251+
delete this.query.$or;
252+
return '';
253+
}
272254

273-
return text;
255+
let regex: string;
256+
let options = 'i';
257+
258+
const match = text.match(/^\/(.+)\/([imxs]*)$/);
259+
260+
if (match) {
261+
regex = match[1];
262+
options = match[2] || 'i';
263+
}
264+
265+
else if (this.forceRegex) {
266+
regex = text;
274267
}
268+
269+
else {
270+
regex = escapeRegExp(text);
271+
}
272+
273+
this.query.$or = [
274+
{ msg: { $regex: regex, $options: options } },
275+
{ 'attachments.text': { $regex: regex, $options: options } },
276+
{ 'attachments.title': { $regex: regex, $options: options } },
277+
{ 'attachments.description': { $regex: regex, $options: options } },
278+
];
279+
280+
return text;
281+
}
275282

276283
parse(text: string) {
277284
[

apps/meteor/tests/unit/server/lib/parseMessageSearchQuery.spec.ts

Lines changed: 21 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ describe('parseMessageSearchQuery', () => {
1010

1111
const utcOffset = new Date().getTimezoneOffset() / 60;
1212

13+
const textSearch = (value: string) => ({
14+
$or: [
15+
{ msg: { $regex: value, $options: 'i' } },
16+
{ 'attachments.text': { $regex: value, $options: 'i' } },
17+
{ 'attachments.title': { $regex: value, $options: 'i' } },
18+
{ 'attachments.description': { $regex: value, $options: 'i' } },
19+
],
20+
});
21+
1322
[
1423
{
1524
text: 'from:rodrigo mention:gabriel chat',
1625
query: {
1726
'u.username': { $regex: 'rodrigo', $options: 'i' },
1827
'mentions.username': { $regex: 'gabriel', $options: 'i' },
19-
'$text': { $search: 'chat' },
20-
},
21-
options: {
22-
projection: { score: { $meta: 'textScore' } },
23-
sort: { ts: -1 },
24-
skip: 0,
25-
limit: 20,
28+
...textSearch('chat'),
2629
},
30+
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
2731
},
2832
{
2933
text: 'has:star',
@@ -35,143 +39,50 @@ describe('parseMessageSearchQuery', () => {
3539
query: { 'urls.0': { $exists: true } },
3640
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
3741
},
38-
{
39-
text: 'has:link',
40-
query: { 'urls.0': { $exists: true } },
41-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
42-
},
4342
{
4443
text: 'is:pinned',
4544
query: { pinned: true },
4645
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
4746
},
48-
{
49-
text: 'has:pin',
50-
query: { pinned: true },
51-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
52-
},
53-
{
54-
text: 'has:location',
55-
query: { location: { $exists: true } },
56-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
57-
},
58-
{
59-
text: 'has:map',
60-
query: { location: { $exists: true } },
61-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
62-
},
6347
{
6448
text: 'label:label',
6549
query: { 'attachments.0.labels': { $regex: 'label', $options: 'i' } },
6650
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
6751
},
68-
{
69-
text: 'label:label1 label:label2',
70-
query: { 'attachments.0.labels': { $regex: 'label2', $options: 'i' } },
71-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
72-
},
7352
{
7453
text: 'file-title:title',
7554
query: { 'attachments.title': { $regex: 'title', $options: 'i' } },
7655
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
7756
},
7857
{
7958
text: 'file-desc:description',
80-
query: {
81-
'attachments.description': { $regex: 'description', $options: 'i' },
82-
},
83-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
84-
},
85-
{
86-
text: 'file-desc:"description"',
87-
query: {
88-
'attachments.description': { $regex: 'description', $options: 'i' },
89-
},
90-
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
91-
},
92-
{
93-
text: 'file-title:"monthly report"',
94-
query: {
95-
'attachments.title': { $regex: 'monthly report', $options: 'i' },
96-
},
59+
query: { 'attachments.description': { $regex: 'description', $options: 'i' } },
9760
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
9861
},
9962
{
10063
text: 'file-desc:отчет follow-up needed',
10164
query: {
10265
'attachments.description': { $regex: 'отчет', $options: 'i' },
103-
'$text': { $search: 'follow-up needed' },
66+
...textSearch('follow-up needed'),
10467
},
105-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
68+
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
10669
},
10770
{
10871
text: 'file-title:"🚀 launch plan" notes later',
10972
query: {
11073
'attachments.title': { $regex: '🚀 launch plan', $options: 'i' },
111-
'$text': { $search: 'notes later' },
112-
},
113-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
114-
},
115-
{
116-
text: 'notes later file-title:"🚀 launch plan"',
117-
query: {
118-
'attachments.title': { $regex: '🚀 launch plan', $options: 'i' },
119-
'$text': { $search: 'notes later' },
74+
...textSearch('notes later'),
12075
},
121-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
76+
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
12277
},
12378
{
12479
text: 'file-desc:report2024 file-title:Q1-review pending',
12580
query: {
12681
'attachments.description': { $regex: 'report2024', $options: 'i' },
12782
'attachments.title': { $regex: 'Q1\\-review', $options: 'i' },
128-
'$text': { $search: 'pending' },
83+
...textSearch('pending'),
12984
},
130-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
131-
},
132-
{
133-
text: 'file-desc:"中 文 测 试" file-title:"报 表" 其他内容',
134-
query: {
135-
'attachments.description': { $regex: '中 文 测 试', $options: 'i' },
136-
'attachments.title': { $regex: '报 表', $options: 'i' },
137-
'$text': { $search: '其他内容' },
138-
},
139-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
140-
},
141-
{
142-
text: 'file-desc:отчет file-title:"финансовый план" завершено',
143-
query: {
144-
'attachments.description': { $regex: 'отчет', $options: 'i' },
145-
'attachments.title': { $regex: 'финансовый план', $options: 'i' },
146-
'$text': { $search: 'завершено' },
147-
},
148-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
149-
},
150-
{
151-
text: 'file-title:"🔥 final version" confirm now',
152-
query: {
153-
'attachments.title': { $regex: '🔥 final version', $options: 'i' },
154-
'$text': { $search: 'confirm now' },
155-
},
156-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
157-
},
158-
{
159-
text: 'file-desc:"launch" from:username new plans',
160-
query: {
161-
'attachments.description': { $regex: 'launch', $options: 'i' },
162-
'u.username': { $regex: 'username', $options: 'i' },
163-
'$text': { $search: 'new plans' },
164-
},
165-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
166-
},
167-
{
168-
text: 'mention:someone file-desc:"budget 💰" file update',
169-
query: {
170-
'attachments.description': { $regex: 'budget 💰', $options: 'i' },
171-
'mentions.username': { $regex: 'someone', $options: 'i' },
172-
'$text': { $search: 'file update' },
173-
},
174-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
85+
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
17586
},
17687
{
17788
text: 'before:01-01-2023',
@@ -195,8 +106,8 @@ describe('parseMessageSearchQuery', () => {
195106
},
196107
{
197108
text: 'hello world',
198-
query: { $text: { $search: 'hello world' } },
199-
options: { projection: { score: { $meta: 'textScore' } }, sort: { ts: -1 }, skip: 0, limit: 20 },
109+
query: textSearch('hello world'),
110+
options: { projection: {}, sort: { ts: -1 }, skip: 0, limit: 20 },
200111
},
201112
].forEach(({ text, query: expectedQuery, options: expectedOptions }) => {
202113
it(`should parse ${JSON.stringify(text)}`, () => {
@@ -205,4 +116,4 @@ describe('parseMessageSearchQuery', () => {
205116
expect(options).to.deep.equal(expectedOptions);
206117
});
207118
});
208-
});
119+
});

0 commit comments

Comments
 (0)