Skip to content

Commit 1569355

Browse files
committed
fix(confluence): paginate space selector dropdown
Confluence v2 spaces endpoint caps at limit=250 per page. The selector endpoint was making one request and silently dropping every space past the first page, which is why some spaces only worked when entered as a manual spaceKey. Now follows _links.next cursor up to 20 pages (5000 spaces).
1 parent 2b1e223 commit 1569355

1 file changed

Lines changed: 51 additions & 25 deletions

File tree

  • apps/sim/app/api/tools/confluence/selector-spaces

apps/sim/app/api/tools/confluence/selector-spaces/route.ts

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,35 +83,61 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
8383
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
8484
}
8585

86-
const url = `https://api.atlassian.com/ex/confluence/${cloudIdValidation.sanitized}/wiki/api/v2/spaces?limit=250`
87-
88-
const response = await fetch(url, {
89-
method: 'GET',
90-
headers: {
91-
Accept: 'application/json',
92-
Authorization: `Bearer ${accessToken}`,
93-
},
94-
})
86+
const baseUrl = `https://api.atlassian.com/ex/confluence/${cloudIdValidation.sanitized}/wiki/api/v2/spaces`
87+
const PAGE_LIMIT = 250
88+
const MAX_PAGES = 20
89+
const spaces: { id: string; name: string; key: string }[] = []
90+
let cursor: string | undefined
91+
let pageCount = 0
92+
93+
while (pageCount < MAX_PAGES) {
94+
const params = new URLSearchParams({ limit: String(PAGE_LIMIT) })
95+
if (cursor) params.set('cursor', cursor)
96+
const url = `${baseUrl}?${params.toString()}`
9597

96-
if (!response.ok) {
97-
const errorText = await response.text()
98-
logger.error('Confluence API error response:', {
99-
status: response.status,
100-
statusText: response.statusText,
101-
error: errorText,
98+
const response = await fetch(url, {
99+
method: 'GET',
100+
headers: {
101+
Accept: 'application/json',
102+
Authorization: `Bearer ${accessToken}`,
103+
},
102104
})
103-
return NextResponse.json(
104-
{ error: parseAtlassianErrorMessage(response.status, response.statusText, errorText) },
105-
{ status: response.status }
106-
)
105+
106+
if (!response.ok) {
107+
const errorText = await response.text()
108+
logger.error('Confluence API error response:', {
109+
status: response.status,
110+
statusText: response.statusText,
111+
error: errorText,
112+
})
113+
return NextResponse.json(
114+
{ error: parseAtlassianErrorMessage(response.status, response.statusText, errorText) },
115+
{ status: response.status }
116+
)
117+
}
118+
119+
const data = await response.json()
120+
for (const space of data.results || []) {
121+
spaces.push({ id: space.id, name: space.name, key: space.key })
122+
}
123+
124+
const nextLink = data._links?.next as string | undefined
125+
if (!nextLink) break
126+
try {
127+
cursor = new URL(nextLink, 'https://placeholder').searchParams.get('cursor') || undefined
128+
} catch {
129+
cursor = undefined
130+
}
131+
if (!cursor) break
132+
pageCount += 1
107133
}
108134

109-
const data = await response.json()
110-
const spaces = (data.results || []).map((space: { id: string; name: string; key: string }) => ({
111-
id: space.id,
112-
name: space.name,
113-
key: space.key,
114-
}))
135+
if (pageCount >= MAX_PAGES) {
136+
logger.warn('Confluence space listing hit pagination cap', {
137+
cap: MAX_PAGES * PAGE_LIMIT,
138+
returned: spaces.length,
139+
})
140+
}
115141

116142
return NextResponse.json({ spaces })
117143
} catch (error) {

0 commit comments

Comments
 (0)