From a70ccddef5b1187b115dd2e2836598384ad83e0c Mon Sep 17 00:00:00 2001 From: Waleed Date: Sat, 4 Apr 2026 16:50:04 -0700 Subject: [PATCH] fix(kb): fix Linear connector GraphQL type errors and tag slot reuse (#3961) * fix(kb): fix Linear connector GraphQL type errors and tag slot reuse * fix(kb): simplify tag slot reuse, revert Linear GraphQL types to String Clean up newTagSlotMapping into direct assignment, remove unnecessary comment, and revert ID! back to String! to match Linear SDK types. Co-Authored-By: Claude Opus 4.6 * fix(kb): use ID! type for Linear GraphQL filter variables * fix(kb): verify field type when reusing existing tag slots Add fieldType check to the tag slot reuse logic so a connector with a matching displayName but different fieldType falls through to fresh slot allocation instead of silently reusing an incompatible slot. Co-Authored-By: Claude Opus 4.6 * fix(kb): enable search on connector selector dropdowns --------- Co-authored-by: Claude Opus 4.6 --- .../api/knowledge/[id]/connectors/route.ts | 26 ++++++++++++++++--- .../components/connector-selector-field.tsx | 2 ++ apps/sim/connectors/linear/linear.ts | 6 ++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/apps/sim/app/api/knowledge/[id]/connectors/route.ts b/apps/sim/app/api/knowledge/[id]/connectors/route.ts index 4b01430f16..92bd8013ee 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/route.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/route.ts @@ -162,19 +162,39 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ } const tagSlotMapping: Record = {} + let newTagSlots: Record = {} if (connectorConfig.tagDefinitions?.length) { const disabledIds = new Set((sourceConfig.disabledTagIds as string[] | undefined) ?? []) const enabledDefs = connectorConfig.tagDefinitions.filter((td) => !disabledIds.has(td.id)) const existingDefs = await db - .select({ tagSlot: knowledgeBaseTagDefinitions.tagSlot }) + .select({ + tagSlot: knowledgeBaseTagDefinitions.tagSlot, + displayName: knowledgeBaseTagDefinitions.displayName, + fieldType: knowledgeBaseTagDefinitions.fieldType, + }) .from(knowledgeBaseTagDefinitions) .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) const usedSlots = new Set(existingDefs.map((d) => d.tagSlot)) - const { mapping, skipped: skippedTags } = allocateTagSlots(enabledDefs, usedSlots) + const existingByName = new Map( + existingDefs.map((d) => [d.displayName, { tagSlot: d.tagSlot, fieldType: d.fieldType }]) + ) + + const defsNeedingSlots: typeof enabledDefs = [] + for (const td of enabledDefs) { + const existing = existingByName.get(td.displayName) + if (existing && existing.fieldType === td.fieldType) { + tagSlotMapping[td.id] = existing.tagSlot + } else { + defsNeedingSlots.push(td) + } + } + + const { mapping, skipped: skippedTags } = allocateTagSlots(defsNeedingSlots, usedSlots) Object.assign(tagSlotMapping, mapping) + newTagSlots = mapping for (const name of skippedTags) { logger.warn(`[${requestId}] No available slots for "${name}"`) @@ -208,7 +228,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ throw new Error('Knowledge base not found') } - for (const [semanticId, slot] of Object.entries(tagSlotMapping)) { + for (const [semanticId, slot] of Object.entries(newTagSlots)) { const td = connectorConfig.tagDefinitions!.find((d) => d.id === semanticId)! await createTagDefinition( { diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field.tsx index 441a4a9313..b2b14cc536 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field.tsx @@ -79,6 +79,8 @@ export function ConnectorSelectorField({ options={comboboxOptions} value={value || undefined} onChange={onChange} + searchable + searchPlaceholder={`Search ${field.title.toLowerCase()}...`} placeholder={ !credentialId ? 'Connect an account first' diff --git a/apps/sim/connectors/linear/linear.ts b/apps/sim/connectors/linear/linear.ts index cd779b3385..fbcb2dde60 100644 --- a/apps/sim/connectors/linear/linear.ts +++ b/apps/sim/connectors/linear/linear.ts @@ -119,7 +119,7 @@ const ISSUE_FIELDS = ` ` const ISSUE_BY_ID_QUERY = ` - query GetIssue($id: String!) { + query GetIssue($id: ID!) { issue(id: $id) { ${ISSUE_FIELDS} } @@ -147,13 +147,13 @@ function buildIssuesQuery(sourceConfig: Record): { const variables: Record = {} if (teamId) { - varDefs.push('$teamId: String!') + varDefs.push('$teamId: ID!') filterClauses.push('team: { id: { eq: $teamId } }') variables.teamId = teamId } if (projectId) { - varDefs.push('$projectId: String!') + varDefs.push('$projectId: ID!') filterClauses.push('project: { id: { eq: $projectId } }') variables.projectId = projectId }