diff --git a/apps/docs/content/docs/en/tools/confluence.mdx b/apps/docs/content/docs/en/tools/confluence.mdx index 7ee0f0e73e..58301d2891 100644 --- a/apps/docs/content/docs/en/tools/confluence.mdx +++ b/apps/docs/content/docs/en/tools/confluence.mdx @@ -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 | @@ -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` diff --git a/apps/sim/app/api/tools/confluence/page-versions/route.ts b/apps/sim/app/api/tools/confluence/page-versions/route.ts index 9d7c162060..cdd77f4e76 100644 --- a/apps/sim/app/api/tools/confluence/page-versions/route.ts +++ b/apps/sim/app/api/tools/confluence/page-versions/route.ts @@ -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') @@ -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 | 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 diff --git a/apps/sim/tools/confluence/get_page_version.ts b/apps/sim/tools/confluence/get_page_version.ts index c162e25465..dc496b38a2 100644 --- a/apps/sim/tools/confluence/get_page_version.ts +++ b/apps/sim/tools/confluence/get_page_version.ts @@ -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 { @@ -14,6 +18,8 @@ export interface ConfluenceGetPageVersionResponse { output: { ts: string pageId: string + title: string | null + content: string | null version: { number: number message: string | null @@ -25,6 +31,12 @@ export interface ConfluenceGetPageVersionResponse { prevVersion: number | null nextVersion: number | null } + body: { + storage?: { + value: string + representation: string + } + } | null } } @@ -100,6 +112,8 @@ 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, @@ -107,6 +121,7 @@ export const confluenceGetPageVersionTool: ToolConfig< authorId: null, createdAt: null, }, + body: data.body ?? null, }, } }, @@ -114,10 +129,29 @@ export const confluenceGetPageVersionTool: ToolConfig< 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, + }, }, } diff --git a/apps/sim/tools/confluence/utils.ts b/apps/sim/tools/confluence/utils.ts index 2f57929654..d7e55a2c56 100644 --- a/apps/sim/tools/confluence/utils.ts +++ b/apps/sim/tools/confluence/utils.ts @@ -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,