diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx
index 55760bb818d..875ff7d6bd4 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx
@@ -1,7 +1,7 @@
'use client'
import { useMemo, useState } from 'react'
-import { ArrowLeft, ArrowLeftRight, Plus, Search } from 'lucide-react'
+import { ArrowLeft, ArrowLeftRight, Info, Plus, Search } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
Button,
@@ -347,12 +347,28 @@ export function AddConnectorModal({
return (
-
- {field.description && (
-
{field.description}
- )}
{field.type === 'selector' && field.selectorKey ? (
-
- {field.title}
- {field.required && *}
-
+
+
+ {field.title}
+ {field.required && *}
+
+ {field.description && (
+
+
+
+
+ {field.description}
+
+ )}
+
{hasCanonicalPair && canonicalId && (
@@ -406,9 +422,6 @@ function SettingsTab({
)}
- {field.description && (
- {field.description}
- )}
{field.type === 'selector' && field.selectorKey ? (
{
+ if (!multiSelect || !multiSelectValues || multiSelectValues.length === 0) return null
+ const labelFor = (v: string) => allOptions.find((opt) => opt.value === v)?.label ?? v
+ if (multiSelectValues.length === 1) return labelFor(multiSelectValues[0])
+ if (multiSelectValues.length === 2) {
+ return `${labelFor(multiSelectValues[0])}, ${labelFor(multiSelectValues[1])}`
+ }
+ return `${labelFor(multiSelectValues[0])}, ${labelFor(multiSelectValues[1])} +${multiSelectValues.length - 2}`
+ }, [multiSelect, multiSelectValues, allOptions])
+
/**
* Filter options based on current value or search query
*/
@@ -590,11 +606,11 @@ const Combobox = memo(
- {selectedOption ? selectedOption.label : placeholder}
+ {multiSelectLabel ?? (selectedOption ? selectedOption.label : placeholder)}
{
it('follower does a final read after timeout to catch a just-finished leader', async () => {
redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false)
- // pollInterval=5, maxWait=9 → loop exits after 2 in-loop polls (T+5, T+10);
- // the third call (polls=3) is the post-deadline last-chance read.
- let polls = 0
- const onFollower = vi.fn(async () => {
- polls += 1
- if (polls <= 2) return null
- return 'late-leader'
- })
+ /**
+ * The intent: after the in-loop poll deadline is reached, the follower
+ * does exactly one more (last-chance) `onFollower` call to catch a leader
+ * that finished between the previous poll and the timeout. Using fake
+ * timers makes the timing deterministic — pollInterval=10 and maxWait=15
+ * cause two in-loop polls (T+10, T+20) and one last-chance read (T+20),
+ * but the schedule is driven by mocked time, not the CI wall clock.
+ */
+ vi.useFakeTimers()
+ try {
+ let polls = 0
+ const onFollower = vi.fn(async () => {
+ polls += 1
+ if (polls <= 2) return null
+ return 'late-leader'
+ })
- const result = await withLeaderLock({
- key: 'k',
- pollIntervalMs: 5,
- maxWaitMs: 9,
- onLeader: async () => 'should-not-run',
- onFollower,
- })
+ const promise = withLeaderLock({
+ key: 'k',
+ pollIntervalMs: 10,
+ maxWaitMs: 15,
+ onLeader: async () => 'should-not-run',
+ onFollower,
+ })
+
+ await vi.advanceTimersByTimeAsync(30)
+ const result = await promise
- expect(result).toBe('late-leader')
- expect(onFollower).toHaveBeenCalledTimes(3)
+ expect(result).toBe('late-leader')
+ expect(onFollower).toHaveBeenCalledTimes(3)
+ } finally {
+ vi.useRealTimers()
+ }
})
it('follower returns null after timeout', async () => {
redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false)
- const result = await withLeaderLock({
- key: 'k',
- pollIntervalMs: 5,
- maxWaitMs: 20,
- onLeader: async () => 'should-not-run',
- onFollower: async () => null,
- })
+ vi.useFakeTimers()
+ try {
+ const onFollower = vi.fn(async () => null)
+ const promise = withLeaderLock({
+ key: 'k',
+ pollIntervalMs: 10,
+ maxWaitMs: 25,
+ onLeader: async () => 'should-not-run',
+ onFollower,
+ })
+
+ await vi.advanceTimersByTimeAsync(50)
+ const result = await promise
- expect(result).toBeNull()
+ expect(result).toBeNull()
+ } finally {
+ vi.useRealTimers()
+ }
})
it('only one of N concurrent callers acquires the lock', async () => {