Skip to content

Commit 28e91f7

Browse files
authored
fix(search): keep the Jira half-terms summary tier within the all-terms match (#9129)
* fix(search): keep the Jira half-terms summary tier within the all-terms match * test(search): normalize Jira fake rows before filtering
1 parent 200b219 commit 28e91f7

2 files changed

Lines changed: 31 additions & 11 deletions

File tree

‎apps/sim/lib/sim-search/live/atlassian.test.ts‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ describe('Confluence plain search ranking', () => {
334334

335335
describe('Jira plain search ranking', () => {
336336
const search = '/ex/jira/cloud/rest/api/3/search/jql'
337-
type Issue = string | { key: string; summary: string }
337+
/** `outsideMatch`: the issue lacks a query term, so a `text ~` all-terms clause drops it. */
338+
type Issue = string | { key: string; summary: string; outsideMatch?: boolean }
338339
/**
339340
* Answers the all-terms query with `exact`, the all-terms summary query with `titled`, and the
340341
* half-the-terms summary query with `half`, or their failures.
@@ -344,7 +345,8 @@ describe('Jira plain search ranking', () => {
344345
titled: Issue[] | (() => never),
345346
half: Issue[] | (() => never) = []
346347
) {
347-
const toRow = (row: Issue) => (typeof row === 'string' ? { key: row, summary: row } : row)
348+
const toRow = (row: Issue): Exclude<Issue, string> =>
349+
typeof row === 'string' ? { key: row, summary: row } : row
348350
const issues = (rows: Issue[]) =>
349351
rows.map(toRow).map(({ key, summary }) => ({ key, fields: { summary } }))
350352
const json = vi.fn(
@@ -354,7 +356,9 @@ describe('Jira plain search ranking', () => {
354356
const jql = options?.body?.jql ?? ''
355357
if (jql.includes('summary ~')) {
356358
const rows = jql.includes(' OR ') ? half : titled
357-
const picked = typeof rows === 'function' ? rows() : rows
359+
const picked = (typeof rows === 'function' ? rows() : rows)
360+
.map(toRow)
361+
.filter((row) => !(row.outsideMatch && jql.includes('text ~')))
358362
return { issues: issues(picked).slice(0, options?.body?.maxResults) }
359363
}
360364
return { issues: issues(exact.ids), ...(exact.more ? { nextPageToken: 'page-2' } : {}) }
@@ -502,4 +506,17 @@ describe('Jira plain search ranking', () => {
502506
const page = await searchAtlassian(api, 'jira', query)
503507
expect(page.documents.map((document) => document.id)).toEqual(['ALL-1', 'HALF-1', 'NEW-3'])
504508
})
509+
510+
it('keeps half-the-terms summaries to issues that match every term somewhere', async () => {
511+
const api = jira(
512+
overflowing,
513+
[],
514+
[
515+
{ key: 'OUTSIDE-1', summary: 'Merged results dashboard', outsideMatch: true },
516+
{ key: 'INSIDE-1', summary: 'Merged results broke the dashboard' },
517+
]
518+
)
519+
const page = await searchAtlassian(api, 'jira', query)
520+
expect(page.documents.map((document) => document.id)).toEqual(['INSIDE-1', 'NEW-3', 'NEW-2'])
521+
})
505522
})

‎apps/sim/lib/sim-search/live/atlassian.ts‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ async function sites(client: NativeClient) {
118118
}
119119

120120
/**
121-
* Pair matches fetched for the half-the-terms summary fallback. Jira returns them newest first,
122-
* not by relevance, and the page is then filtered to summaries holding enough terms, so it starts
123-
* from a wider pool than the caller's limit or the any-term fallback's.
121+
* Issues fetched for the half-the-terms summary tier: summary pair matches within the all-terms
122+
* match. Jira returns them newest first, not by relevance, and the page is then filtered to
123+
* summaries holding enough terms, so it starts from a wider pool than the caller's limit or the
124+
* any-term fallback's.
124125
*/
125126
const HALF_TERM_CANDIDATES = 50
126127

@@ -226,17 +227,19 @@ export async function searchAtlassian(
226227
next: exact.next,
227228
}
228229
// JQL has no relevance order, so an all-terms match larger than a page comes back newest
229-
// first; issues whose summary holds every term lead it instead, then those holding at
230-
// least half of three or more terms (most terms first).
230+
// first. Within that match, issues whose summary holds every term lead it instead, then
231+
// those whose summary holds at least half of three or more terms (most terms first); a
232+
// summary match never brings in an issue lacking one of the terms.
231233
if (!text || input.native || order || input.textMatch === 'any' || !exact.next)
232234
return exactPage
233235
const terms = stemmedTerms(text)
234236
const [every, half] = await Promise.allSettled([
235237
searchIssues(jiraTextClause(text, 'all', 'summary')),
236238
terms.count >= 3
237-
? searchIssues(jiraTextClause(text, 'any', 'summary'), {
238-
maxResults: HALF_TERM_CANDIDATES,
239-
})
239+
? searchIssues(
240+
`${jiraTextClause(text, 'any', 'summary')} AND ${jiraTextClause(text)}`,
241+
{ maxResults: HALF_TERM_CANDIDATES }
242+
)
240243
: undefined,
241244
])
242245
// A cancelled search stops here; any other failure keeps the all-terms page.

0 commit comments

Comments
 (0)