Skip to content

Commit ac07b0c

Browse files
committed
fix(connectors): order Confluence spaces by name and stop implying a space is missing
Confluence defaults /spaces to id-ascending, which interleaves the site's personal spaces (one per user) with team spaces in arbitrary order. The selector drains pages in the background and filters client-side, so on a large site a space can be absent from the dropdown purely because its id sorts late — it reads as "this space does not exist" and sends users off to type the key by hand. Sorting by name makes the drained prefix an alphabetical prefix. The connector selector field also ignored the drain state the shared hook already exposes, so a still-filling list showed "No spaces found". It now reports that the list is still loading, and says so honestly when the drain stops at its page cap.
1 parent cb3611b commit ac07b0c

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export const dynamic = 'force-dynamic'
2121

2222
const PAGE_LIMIT = 250
2323

24+
/** Confluence v2 `/spaces` accepts only `id` or `name` (prefix `-` to reverse). */
25+
const SPACE_SORT_FIELD = 'name'
26+
2427
type SpaceStatus = 'current' | 'archived'
2528

2629
/**
@@ -101,7 +104,19 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
101104
const baseUrl = `https://api.atlassian.com/ex/confluence/${cloudIdValidation.sanitized}/wiki/api/v2/spaces`
102105
const { status, inner } = parseCursor(cursor)
103106

104-
const params = new URLSearchParams({ limit: String(PAGE_LIMIT), status })
107+
/**
108+
* Confluence defaults to id-ascending, which interleaves the site's personal
109+
* spaces (one per user) with team spaces in effectively arbitrary order. The
110+
* dropdown drains pages in the background and filters client-side, so with an
111+
* id-ordered stream a space can be absent from the list purely because its id
112+
* sorts late. Sorting by name makes the drained prefix an alphabetical prefix,
113+
* so a space the user is looking for appears where they expect it.
114+
*/
115+
const params = new URLSearchParams({
116+
limit: String(PAGE_LIMIT),
117+
status,
118+
sort: SPACE_SORT_FIELD,
119+
})
105120
if (inner) params.set('cursor', inner)
106121
const url = `${baseUrl}?${params.toString()}`
107122

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,31 @@ export function ConnectorSelectorField({
6262
}, [field.dependsOn, sourceConfig, configFields, canonicalModes])
6363

6464
const isEnabled = !disabled && !!credentialId && depsResolved
65-
const { data: options = [], isLoading } = useSelectorOptions(field.selectorKey, {
65+
const {
66+
data: options = [],
67+
isLoading,
68+
hasMore,
69+
isFetchingMore,
70+
truncated,
71+
} = useSelectorOptions(field.selectorKey, {
6672
context,
6773
enabled: isEnabled,
6874
})
6975

76+
/**
77+
* Paginated selectors drain in the background and the combobox filters
78+
* client-side, so a not-yet-drained option is genuinely absent from the list.
79+
* Saying "none found" there reads as "it does not exist" and sends users off to
80+
* enter the value by hand — say the list is still filling instead, and be honest
81+
* when the drain stopped at its page cap.
82+
*/
83+
const emptyMessage = useMemo(() => {
84+
const noun = field.title.toLowerCase()
85+
if (hasMore || isFetchingMore) return `Still loading ${noun}…`
86+
if (truncated) return `Too many ${noun} to list — enter the value directly`
87+
return `No ${noun} found`
88+
}, [field.title, hasMore, isFetchingMore, truncated])
89+
7090
const comboboxOptions = useMemo<ComboboxOption[]>(
7191
() => options.map((opt) => ({ label: opt.label, value: opt.id })),
7292
[options]
@@ -99,7 +119,7 @@ export function ConnectorSelectorField({
99119
: field.placeholder || `Select ${field.title.toLowerCase()}`
100120
}
101121
disabled={disabled || !credentialId || !depsResolved}
102-
emptyMessage={`No ${field.title.toLowerCase()} found`}
122+
emptyMessage={emptyMessage}
103123
/>
104124
)
105125
}
@@ -120,7 +140,7 @@ export function ConnectorSelectorField({
120140
: field.placeholder || `Select ${field.title.toLowerCase()}`
121141
}
122142
disabled={disabled || !credentialId || !depsResolved}
123-
emptyMessage={`No ${field.title.toLowerCase()} found`}
143+
emptyMessage={emptyMessage}
124144
/>
125145
)
126146
}

0 commit comments

Comments
 (0)