Skip to content

Commit 727875a

Browse files
committed
fix(search): use search-response spaces for verification and fall back for legacy blog-post references
1 parent ba8c7d5 commit 727875a

4 files changed

Lines changed: 47 additions & 8 deletions

File tree

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it, vi } from 'vitest'
22
import { readAtlassian, searchAtlassian } from '@/lib/sim-search/live/atlassian'
3+
import { NativeSearchError } from '@/lib/sim-search/live/http'
34
import type { NativeClient } from '@/lib/sim-search/live/types'
45

56
const SITE = { id: 'cloud', url: 'https://acme.atlassian.net' }
@@ -50,7 +51,22 @@ describe('Confluence live documents', () => {
5051
content: expect.stringContaining('Shipped search.'),
5152
}
5253
)
53-
expect(api.json).toHaveBeenCalledWith(`${v2}/pages/123`, { query: { 'body-format': 'view' } })
54+
})
55+
56+
it('reads a legacy reference without a kind as a blog post when no page has that id', async () => {
57+
const api: NativeClient = {
58+
json: vi.fn(async (path: string) => {
59+
if (path === '/oauth/token/accessible-resources') return [SITE]
60+
if (path === `${v2}/blogposts/9`)
61+
return { id: '9', title: 'Release notes', body: { view: { value: '<p>Shipped.</p>' } } }
62+
throw new NativeSearchError('unavailable', 'Provider request failed (404).')
63+
}),
64+
text: vi.fn(),
65+
}
66+
await expect(readAtlassian(api, 'confluence', '9', 'cloud')).resolves.toMatchObject({
67+
kind: 'blogpost',
68+
content: expect.stringContaining('Shipped.'),
69+
})
5470
})
5571

5672
it('reads a space result as its homepage, keeping the space as the document', async () => {
@@ -71,7 +87,6 @@ describe('Confluence live documents', () => {
7187
title: 'Engineering',
7288
content: expect.stringContaining('Team charter.'),
7389
})
74-
expect(api.json).toHaveBeenCalledWith(`${v2}/spaces`, { query: { keys: 'ENG' } })
7590
})
7691

7792
it('records whether a search result is a page, blog post, or space so its read picks the endpoint', async () => {
@@ -82,6 +97,7 @@ describe('Confluence live documents', () => {
8297
content: {
8398
id: '123',
8499
type: 'page',
100+
space: { key: 'ENG' },
85101
title: 'Runbook',
86102
_links: { webui: '/spaces/ENG/pages/123' },
87103
},
@@ -115,5 +131,7 @@ describe('Confluence live documents', () => {
115131
{ id: 'ENG', kind: 'space' },
116132
])
117133
expect(page.documents[2]?.url).toBe('https://acme.atlassian.net/wiki/spaces/ENG')
134+
expect(page.documents[0]?.accessMetadata).toEqual({ spaceKey: 'ENG' })
135+
expect(page.documents[2]?.accessMetadata).toEqual({ spaceKey: 'ENG' })
118136
})
119137
})

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function page(row: Record<string, unknown>, cloudId: string, site: string): Nati
7171
return {
7272
id: string(space.key),
7373
kind: 'space',
74+
accessMetadata: { spaceKey: string(space.key) },
7475
container: cloudId,
7576
title: string(row.title) || string(space.name),
7677
url: `${site}/wiki${string(row.url) || `/spaces/${segment(string(space.key))}`}`,
@@ -81,9 +82,11 @@ function page(row: Record<string, unknown>, cloudId: string, site: string): Nati
8182
const content = Object.keys(object(row.content)).length ? object(row.content) : row
8283
const links = object(content._links)
8384
const version = object(content.version)
85+
const spaceKey = string(object(content.space).key)
8486
return {
8587
id: string(content.id),
8688
kind: string(content.type) === 'blogpost' ? 'blogpost' : 'page',
89+
...(spaceKey ? { accessMetadata: { spaceKey } } : {}),
8790
container: cloudId,
8891
title: string(content.title) || string(row.title),
8992
url: `${site}/wiki${string(links.webui) || `/pages/${segment(string(content.id))}`}`,
@@ -174,7 +177,7 @@ export async function searchAtlassian(
174177
order
175178
),
176179
limit: String(input.limit),
177-
expand: 'content.version',
180+
expand: 'content.version,content.space',
178181
...(input.native?.cursor && single ? { cursor: input.native.cursor } : {}),
179182
},
180183
})
@@ -248,11 +251,19 @@ async function readConfluence(
248251
contentId = string(space.homepageId)
249252
contentKind = 'page'
250253
}
254+
const content = (type: string) =>
255+
client.json(`${api}/${type}/${segment(contentId)}`, { query: { 'body-format': 'view' } })
256+
/** A reference issued before kinds were recorded may name a blog post; its page read is a 404. */
251257
const row = object(
252-
await client.json(
253-
`${api}/${contentKind === 'blogpost' ? 'blogposts' : 'pages'}/${segment(contentId)}`,
254-
{ query: { 'body-format': 'view' } }
255-
)
258+
kind === undefined
259+
? await content('pages').catch((error: unknown) => {
260+
if (error instanceof NativeSearchError && error.status === 'unavailable') {
261+
contentKind = 'blogpost'
262+
return content('blogposts')
263+
}
264+
throw error
265+
})
266+
: await content(contentKind === 'blogpost' ? 'blogposts' : 'pages')
256267
)
257268
const pageTitle = string(row.title)
258269
return {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,15 @@ describe('organization search scope enforcement', () => {
204204
).toBe(false)
205205
}
206206
)
207+
it('checks a Confluence search hit by the space its search response named, without requests', async () => {
208+
const verify = createPolicyVerifier('confluence', selected(['ENG']), client({}), '')
209+
expect(await verify({ id: '123', container: 'site', kind: 'page' }, { spaceKey: 'ENG' })).toBe(
210+
true
211+
)
212+
expect(await verify({ id: '124', container: 'site', kind: 'page' }, { spaceKey: 'HR' })).toBe(
213+
false
214+
)
215+
})
207216
it('checks Confluence spaces by key and blog posts through their own endpoint', async () => {
208217
const api = client({
209218
'/ex/confluence/site/wiki/api/v2/blogposts/9': { id: '9', spaceId: '7' },

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ export function createPolicyVerifier(
202202
}
203203
/** v2 reads, like document reads, so the check needs only the granular read scopes. */
204204
const api = `/ex/confluence/${segment(document.container)}/wiki/api/v2`
205-
let spaceKey = document.kind === 'space' ? document.id : ''
205+
/** The search response names each hit's space; only reads without that evidence look it up. */
206+
let spaceKey = document.kind === 'space' ? document.id : string(providerMetadata?.spaceKey)
206207
if (!spaceKey) {
207208
const row = object(
208209
await json(

0 commit comments

Comments
 (0)