Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/docs/content/docs/en/tools/confluence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ Get details about a specific version of a Confluence page.
| --------- | ---- | ----------- |
| `ts` | string | ISO 8601 timestamp of the operation |
| `pageId` | string | ID of the page |
| `title` | string | Page title at this version |
| `content` | string | Page content with HTML tags stripped at this version |
| `version` | object | Detailed version information |
| ↳ `number` | number | Version number |
| ↳ `message` | string | Version message |
Expand All @@ -336,6 +338,9 @@ Get details about a specific version of a Confluence page.
| ↳ `collaborators` | array | List of collaborator account IDs for this version |
| ↳ `prevVersion` | number | Previous version number |
| ↳ `nextVersion` | number | Next version number |
| `body` | object | Raw page body content in storage format at this version |
| ↳ `value` | string | The content value in the specified format |
| ↳ `representation` | string | Content representation type |

### `confluence_list_page_properties`

Expand Down
83 changes: 60 additions & 23 deletions apps/sim/app/api/tools/confluence/page-versions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
import { cleanHtmlContent, getConfluenceCloudId } from '@/tools/confluence/utils'

const logger = createLogger('ConfluencePageVersionsAPI')

Expand Down Expand Up @@ -55,42 +55,79 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}

// If versionNumber is provided, get specific version
// If versionNumber is provided, get specific version with page content
if (versionNumber !== undefined && versionNumber !== null) {
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
const versionUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
const pageUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}?version=${versionNumber}&body-format=storage`

logger.info(`Fetching version ${versionNumber} for page ${pageId}`)

const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

if (!response.ok) {
const errorData = await response.json().catch(() => null)
const [versionResponse, pageResponse] = await Promise.all([
fetch(versionUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}),
fetch(pageUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}),
])

if (!versionResponse.ok) {
const errorData = await versionResponse.json().catch(() => null)
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
status: versionResponse.status,
statusText: versionResponse.statusText,
error: JSON.stringify(errorData, null, 2),
})
const errorMessage = errorData?.message || `Failed to get page version (${response.status})`
return NextResponse.json({ error: errorMessage }, { status: response.status })
const errorMessage =
errorData?.message || `Failed to get page version (${versionResponse.status})`
return NextResponse.json({ error: errorMessage }, { status: versionResponse.status })
}

const data = await response.json()
const versionData = await versionResponse.json()

let title: string | null = null
let content: string | null = null
let body: Record<string, unknown> | null = null

if (pageResponse.ok) {
const pageData = await pageResponse.json()
title = pageData.title ?? null
body = pageData.body ?? null

const rawContent =
pageData.body?.storage?.value ||
pageData.body?.view?.value ||
pageData.body?.atlas_doc_format?.value ||
''
if (rawContent) {
content = cleanHtmlContent(rawContent)
}
} else {
logger.warn(
`Could not fetch page content for version ${versionNumber}: ${pageResponse.status}`
)
}

return NextResponse.json({
version: {
number: data.number,
message: data.message ?? null,
minorEdit: data.minorEdit ?? false,
authorId: data.authorId ?? null,
createdAt: data.createdAt ?? null,
number: versionData.number,
message: versionData.message ?? null,
minorEdit: versionData.minorEdit ?? false,
authorId: versionData.authorId ?? null,
createdAt: versionData.createdAt ?? null,
},
pageId,
title,
content,
body,
})
}
// List all versions
Expand Down
36 changes: 35 additions & 1 deletion apps/sim/tools/confluence/get_page_version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DETAILED_VERSION_OUTPUT_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
import {
BODY_FORMAT_PROPERTIES,
DETAILED_VERSION_OUTPUT_PROPERTIES,
TIMESTAMP_OUTPUT,
} from '@/tools/confluence/types'
import type { ToolConfig } from '@/tools/types'

export interface ConfluenceGetPageVersionParams {
Expand All @@ -14,6 +18,8 @@ export interface ConfluenceGetPageVersionResponse {
output: {
ts: string
pageId: string
title: string | null
content: string | null
version: {
number: number
message: string | null
Expand All @@ -25,6 +31,12 @@ export interface ConfluenceGetPageVersionResponse {
prevVersion: number | null
nextVersion: number | null
}
body: {
storage?: {
value: string
representation: string
}
} | null
}
}

Expand Down Expand Up @@ -100,24 +112,46 @@ export const confluenceGetPageVersionTool: ToolConfig<
output: {
ts: new Date().toISOString(),
pageId: data.pageId ?? '',
title: data.title ?? null,
content: data.content ?? null,
version: data.version ?? {
number: 0,
message: null,
minorEdit: false,
authorId: null,
createdAt: null,
},
body: data.body ?? null,
},
}
},

outputs: {
ts: TIMESTAMP_OUTPUT,
pageId: { type: 'string', description: 'ID of the page' },
title: { type: 'string', description: 'Page title at this version', optional: true },
content: {
type: 'string',
description: 'Page content with HTML tags stripped at this version',
optional: true,
},
version: {
type: 'object',
description: 'Detailed version information',
properties: DETAILED_VERSION_OUTPUT_PROPERTIES,
},
body: {
type: 'object',
description: 'Raw page body content in storage format at this version',
properties: {
storage: {
type: 'object',
description: 'Body in storage format (Confluence markup)',
properties: BODY_FORMAT_PROPERTIES,
optional: true,
},
},
optional: true,
},
},
}
14 changes: 11 additions & 3 deletions apps/sim/tools/confluence/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,21 @@ function stripHtmlTags(html: string): string {
return text.trim()
}

/**
* Strips HTML tags and decodes HTML entities from raw Confluence content.
*/
export function cleanHtmlContent(rawContent: string): string {
let content = stripHtmlTags(rawContent)
content = decodeHtmlEntities(content)
content = content.replace(/\s+/g, ' ').trim()
return content
}

export function transformPageData(data: any) {
const rawContent =
data.body?.storage?.value || data.body?.view?.value || data.body?.atlas_doc_format?.value || ''

let cleanContent = stripHtmlTags(rawContent)
cleanContent = decodeHtmlEntities(cleanContent)
cleanContent = cleanContent.replace(/\s+/g, ' ').trim()
const cleanContent = cleanHtmlContent(rawContent)

return {
success: true,
Expand Down