Skip to content

Commit 73d0c66

Browse files
committed
improvement(confluence): expand scopes, persist canonical mode toggle
1 parent 0925e2d commit 73d0c66

5 files changed

Lines changed: 89 additions & 19 deletions

File tree

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ export function AddConnectorModal({
157157
for (const [key, value] of Object.entries(resolveSourceConfig())) {
158158
if (value) resolvedConfig[key] = value
159159
}
160-
const finalSourceConfig =
161-
disabledTagIds.size > 0
162-
? { ...resolvedConfig, disabledTagIds: Array.from(disabledTagIds) }
163-
: resolvedConfig
160+
if (disabledTagIds.size > 0) {
161+
resolvedConfig.disabledTagIds = Array.from(disabledTagIds)
162+
}
163+
if (Object.keys(canonicalModes).length > 0) {
164+
resolvedConfig._canonicalModes = canonicalModes
165+
}
166+
const finalSourceConfig = resolvedConfig
164167

165168
createConnector(
166169
{

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,33 @@ import type { SelectorKey } from '@/hooks/selectors/types'
4343

4444
const logger = createLogger('EditConnectorModal')
4545

46-
/** Keys injected by the sync engine — not user-editable */
47-
const INTERNAL_CONFIG_KEYS = new Set(['tagSlotMapping', 'disabledTagIds'])
46+
/** Keys injected by the sync engine or modal state — not user-editable */
47+
const INTERNAL_CONFIG_KEYS = new Set(['tagSlotMapping', 'disabledTagIds', '_canonicalModes'])
48+
49+
const CANONICAL_MODES_KEY = '_canonicalModes'
50+
51+
function readPersistedCanonicalModes(
52+
sourceConfig: Record<string, unknown>
53+
): Record<string, 'basic' | 'advanced'> {
54+
const raw = sourceConfig[CANONICAL_MODES_KEY]
55+
if (!raw || typeof raw !== 'object') return {}
56+
const result: Record<string, 'basic' | 'advanced'> = {}
57+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
58+
if (value === 'basic' || value === 'advanced') result[key] = value
59+
}
60+
return result
61+
}
62+
63+
function didCanonicalModesChange(
64+
current: Record<string, 'basic' | 'advanced'>,
65+
persisted: Record<string, 'basic' | 'advanced'>
66+
): boolean {
67+
const keys = new Set([...Object.keys(persisted), ...Object.keys(current)])
68+
for (const key of keys) {
69+
if ((current[key] ?? 'basic') !== (persisted[key] ?? 'basic')) return true
70+
}
71+
return false
72+
}
4873

4974
interface EditConnectorModalProps {
5075
open: boolean
@@ -87,6 +112,10 @@ export function EditConnectorModal({
87112
return config
88113
})
89114

115+
const [initialCanonicalModes] = useState<Record<string, 'basic' | 'advanced'>>(() =>
116+
readPersistedCanonicalModes(connector.sourceConfig)
117+
)
118+
90119
const {
91120
sourceConfig,
92121
canonicalModes,
@@ -95,7 +124,11 @@ export function EditConnectorModal({
95124
handleFieldChange,
96125
toggleCanonicalMode,
97126
resolveSourceConfig,
98-
} = useConnectorConfigFields({ connectorConfig, initialSourceConfig })
127+
} = useConnectorConfigFields({
128+
connectorConfig,
129+
initialSourceConfig,
130+
initialCanonicalModes,
131+
})
99132

100133
const { mutate: updateConnector, isPending: isSaving } = useUpdateConnector()
101134

@@ -105,12 +138,20 @@ export function EditConnectorModal({
105138

106139
const hasChanges = useMemo(() => {
107140
if (syncInterval !== connector.syncIntervalMinutes) return true
141+
const persisted = readPersistedCanonicalModes(connector.sourceConfig)
142+
if (didCanonicalModesChange(canonicalModes, persisted)) return true
108143
const resolved = resolveSourceConfig()
109144
for (const [key, value] of Object.entries(resolved)) {
110145
if (String(connector.sourceConfig[key] ?? '') !== value) return true
111146
}
112147
return false
113-
}, [resolveSourceConfig, syncInterval, connector.syncIntervalMinutes, connector.sourceConfig])
148+
}, [
149+
resolveSourceConfig,
150+
syncInterval,
151+
connector.syncIntervalMinutes,
152+
connector.sourceConfig,
153+
canonicalModes,
154+
])
114155

115156
const handleSave = () => {
116157
setError(null)
@@ -126,8 +167,18 @@ export function EditConnectorModal({
126167
for (const [key, value] of Object.entries(resolved)) {
127168
if (String(connector.sourceConfig[key] ?? '') !== value) changedEntries[key] = value
128169
}
129-
if (Object.keys(changedEntries).length > 0) {
130-
updates.sourceConfig = { ...connector.sourceConfig, ...changedEntries }
170+
171+
const persistedModes = readPersistedCanonicalModes(connector.sourceConfig)
172+
const modesChanged = didCanonicalModesChange(canonicalModes, persistedModes)
173+
174+
if (Object.keys(changedEntries).length > 0 || modesChanged) {
175+
const next: Record<string, unknown> = { ...connector.sourceConfig, ...changedEntries }
176+
if (Object.keys(canonicalModes).length > 0) {
177+
next[CANONICAL_MODES_KEY] = canonicalModes
178+
} else {
179+
delete next[CANONICAL_MODES_KEY]
180+
}
181+
updates.sourceConfig = next
131182
}
132183

133184
if (Object.keys(updates).length === 0) {

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { ConnectorConfig, ConnectorConfigField } from '@/connectors/types'
77
export interface UseConnectorConfigFieldsOptions {
88
connectorConfig: ConnectorConfig | null
99
initialSourceConfig?: Record<string, string>
10+
initialCanonicalModes?: Record<string, 'basic' | 'advanced'>
1011
}
1112

1213
export interface UseConnectorConfigFieldsResult {
@@ -34,11 +35,14 @@ export interface UseConnectorConfigFieldsResult {
3435
export function useConnectorConfigFields({
3536
connectorConfig,
3637
initialSourceConfig,
38+
initialCanonicalModes,
3739
}: UseConnectorConfigFieldsOptions): UseConnectorConfigFieldsResult {
3840
const [sourceConfig, setSourceConfig] = useState<Record<string, string>>(
3941
() => initialSourceConfig ?? {}
4042
)
41-
const [canonicalModes, setCanonicalModes] = useState<Record<string, 'basic' | 'advanced'>>({})
43+
const [canonicalModes, setCanonicalModes] = useState<Record<string, 'basic' | 'advanced'>>(
44+
() => initialCanonicalModes ?? {}
45+
)
4246

4347
const canonicalGroups = useMemo(() => {
4448
const groups = new Map<string, ConnectorConfigField[]>()

apps/sim/connectors/confluence/confluence.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ConfluenceIcon } from '@/components/icons'
44
import { fetchWithRetry, VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils'
55
import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types'
66
import { htmlToPlainText, joinTagArray, parseTagDate } from '@/connectors/utils'
7-
import { getConfluenceCloudId } from '@/tools/confluence/utils'
7+
import { getConfluenceCloudId, normalizeConfluenceDomainHost } from '@/tools/confluence/utils'
88

99
const logger = createLogger('ConfluenceConnector')
1010

@@ -141,7 +141,15 @@ export const confluenceConnector: ConnectorConfig = {
141141
auth: {
142142
mode: 'oauth',
143143
provider: 'confluence',
144-
requiredScopes: ['read:confluence-content.all', 'read:page:confluence', 'offline_access'],
144+
requiredScopes: [
145+
'read:confluence-content.all',
146+
'read:page:confluence',
147+
'read:blogpost:confluence',
148+
'read:space:confluence',
149+
'read:label:confluence',
150+
'search:confluence',
151+
'offline_access',
152+
],
145153
},
146154

147155
configFields: [
@@ -205,7 +213,7 @@ export const confluenceConnector: ConnectorConfig = {
205213
cursor?: string,
206214
syncContext?: Record<string, unknown>
207215
): Promise<ExternalDocumentList> => {
208-
const domain = sourceConfig.domain as string
216+
const domain = normalizeConfluenceDomainHost(sourceConfig.domain as string)
209217
const spaceKey = sourceConfig.spaceKey as string
210218
const contentType = (sourceConfig.contentType as string) || 'page'
211219
const labelFilter = (sourceConfig.labelFilter as string) || ''
@@ -269,7 +277,7 @@ export const confluenceConnector: ConnectorConfig = {
269277
externalId: string,
270278
syncContext?: Record<string, unknown>
271279
): Promise<ExternalDocument | null> => {
272-
const domain = sourceConfig.domain as string
280+
const domain = normalizeConfluenceDomainHost(sourceConfig.domain as string)
273281
let cloudId = syncContext?.cloudId as string | undefined
274282
if (!cloudId) {
275283
cloudId = await getConfluenceCloudId(domain, accessToken)

apps/sim/tools/confluence/utils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import type { RetryOptions } from '@/lib/knowledge/documents/utils'
22
import { fetchWithRetry } from '@/lib/knowledge/documents/utils'
33

4-
function normalizeDomain(domain: string): string {
5-
return `https://${domain
4+
/**
5+
* Strips protocol and trailing slashes from a Confluence domain to produce
6+
* a bare host (e.g. `yoursite.atlassian.net`).
7+
*/
8+
export function normalizeConfluenceDomainHost(domain: string): string {
9+
return domain
610
.trim()
711
.replace(/^https?:\/\//i, '')
8-
.replace(/\/+$/, '')}`.toLowerCase()
12+
.replace(/\/+$/, '')
913
}
1014

1115
export async function getConfluenceCloudId(
@@ -31,7 +35,7 @@ export async function getConfluenceCloudId(
3135
throw new Error('No Confluence resources found')
3236
}
3337

34-
const normalized = normalizeDomain(domain)
38+
const normalized = `https://${normalizeConfluenceDomainHost(domain)}`.toLowerCase()
3539
const match = resources.find(
3640
(r: { url: string }) => r.url.toLowerCase().replace(/\/+$/, '') === normalized
3741
)

0 commit comments

Comments
 (0)