Skip to content

improvement(kb-connectors): multi-select fields + Slack bot/app message extraction#4711

Merged
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/jira-kb-multi-project-select-v2
May 22, 2026
Merged

improvement(kb-connectors): multi-select fields + Slack bot/app message extraction#4711
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/jira-kb-multi-project-select-v2

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Adds multi-select to 8 KB connectors (Jira, Confluence, Slack, MS Teams, GCal, Gmail, Notion, Linear) via a new multi?: boolean flag on ConnectorConfigField
  • 100% backwards compat: every connector emits byte-identical externalId for existing single-value configs, so the sync engine reconciles in place
  • Fixes Slack connector dropping GitHub-app (and other bot) messages — now extracts content from attachments and Block Kit blocks, including nested attachment.blocks; contentHash bumped to force one-time re-embed
  • Fixes pre-existing Confluence selector bug returning space.id instead of space.key

Type of Change

  • New feature
  • Bug fix

Testing

Tested manually via tsc + connector test suite (86/86 passing). Four rounds of static audit against live API docs for each connector. UI smoke test recommended before merge.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…ge extraction

Adds multi-value support to KB connector configuration fields and applies it
across 8 connectors: Jira (projects), Confluence (spaces), Slack (channels),
Microsoft Teams (channels), Google Calendar (calendars), Gmail (labels),
Notion (databases), and Linear (teams + projects). Each connector emits
byte-identical externalId for legacy single-value configs so existing rows
reconcile in place via the sync engine's externalId-keyed matching.

Framework changes:
- ConnectorConfigField gains `multi?: boolean`
- New `parseMultiValue` helper in @/connectors/utils
- useConnectorConfigFields state model upgraded to string|string[]
- ConnectorSelectorField renders Combobox in multi-select mode when `field.multi`
- Add/edit connector modals handle array values end-to-end

Per-connector specifics:
- Jira: JQL `project in (...)` for 2+ keys, `project = X` for one
- Confluence: routes through CQL `space in (...)` when multi; v2 fast path stays
  for single+no-label; also fixes selector returning space.id instead of space.key
- Slack: loops per channel emitting one document each; extracts text from
  attachments and Block Kit blocks (incl. nested attachment.blocks where GitHub
  embeds PR bodies); contentHash bumped to slack-v2: to force one-time re-embed
- Microsoft Teams: loops per channel within a single team
- Google Calendar: compound cursor across calendars; single-calendar keeps
  legacy externalId/contentHash for zero-churn
- Gmail: (label:A OR label:B) with quoted-form for labels with spaces
- Notion: sequential walk via JSON compound cursor; single-DB keeps bare cursor
- Linear: GraphQL IdComparator.in for multi, eq for single
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 22, 2026 2:24am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 22, 2026

PR Summary

Medium Risk
Moderate risk because it changes connector config shape (single vs multi values), cursor formats, and document ID/hash behavior across several sync sources, which could affect syncing/deduplication if edge cases were missed.

Overview
Adds multi-select connector configuration across the KB connector stack. A new multi?: boolean on ConnectorConfigField allows selector and manual-input fields to accept multiple values, updates the add/edit connector modals + config-field hook to store string[], normalize required-field checks, clear dependent fields correctly, and avoid false “unsaved changes” by deep-comparing scalar/CSV/array equivalents.

Updates multiple connectors to support multi-value filtering. Confluence/Jira/Linear/Gmail/Google Calendar/Notion/Slack/MS Teams now normalize config via parseMultiValue and adjust query construction, validation, and listing accordingly (including multi-entity pagination/cursors for Google Calendar and Notion, and Linear project lookup handling comma-separated team IDs).

Improves Slack ingestion fidelity and forces re-embed. Slack now extracts user-visible text from bot/app messages by parsing attachments and Block Kit blocks (including nested attachment.blocks), relaxes the previous “must have text” filter, supports syncing multiple channels per connector, and bumps the channel document contentHash prefix (slack-v2) to trigger a one-time resync.

Fixes selector ID mapping for Confluence spaces. The Confluence spaces selector now uses space.key (not space.id) as the option ID to match connector config expectations.

Reviewed by Cursor Bugbot for commit 84c3159. Configure here.

…i-array

Existing connectors created before multi-select store sourceConfig values as
scalars (e.g. projectKey: "ENG"). With the field now declared multi: true,
resolveSourceConfig returns an array (["ENG"]), and the original valuesEqual
fell through to a strict reference comparison — falsely flagging unsaved
changes on open and triggering an unnecessary string→array shape rewrite on
save.

valuesEqual now normalizes both sides to string[] via CSV-split when either
is an array, so persisted scalar and in-memory array of the same content
compare equal. Single-value (non-multi) fields keep strict string equality.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR adds multi?: boolean support to eight KB connectors (Jira, Confluence, Slack, MS Teams, GCal, Gmail, Notion, Linear), fixes Slack dropping bot/app messages by extracting Block Kit and legacy attachment content, fixes a Confluence selector emitting space.id instead of space.key, and bumps the Slack content hash to force a one-time re-embed.

  • Multi-select plumbing: a new parseMultiValue utility normalises string | string[] inputs; ConnectorConfigField.multi drives both the Combobox multi-select UI and CSV-split in advanced text input; coerceForField / resolveSourceConfig keep the type contract clean throughout the hook/modal stack.
  • Slack bot-message extraction: extractMessageContent + walkBlockText recursively pull text from attachments, blocks, and nested attachment.blocks; the slack-v2 content-hash prefix forces a one-time re-embed for all previously indexed channels.
  • Backwards compatibility: single-value externalId formats are preserved for all connectors; Notion and GCal use compound JSON cursors for multi-entity pagination while legacy bare cursors fall back gracefully.

Confidence Score: 5/5

Safe to merge. The multi-select plumbing is backward-compatible, the Slack content-hash bump causes a known one-time re-embed, and the Confluence key fix is a clean bug correction.

All connector changes are additive behind the multi flag with existing single-value paths unchanged. The compound JSON cursors for Notion and GCal pagination correctly fall back to legacy bare cursors. The parseMultiValue deduplication, coerceForField type coercion, and valuesEqual order-insensitive comparison are all correctly implemented. No new data-loss or auth boundary issues were found.

No files require special attention, though a UI smoke test of the multi-select Combobox for each connector is recommended before merge as noted in the PR checklist.

Important Files Changed

Filename Overview
apps/sim/connectors/utils.ts Adds parseMultiValue — well-implemented deduplication + trim for both string[] and CSV string inputs with empty-value guard.
apps/sim/connectors/slack/slack.ts Multi-channel support and Block Kit/attachment extraction look correct; slack-v2 hash bump forces one-time re-embed. Throws on channel-not-found (matching Teams behavior), which can abort remaining channels in a multi-channel sync — flagged previously.
apps/sim/connectors/notion/notion.ts Multi-DB pagination uses a well-designed compound JSON cursor with correct legacy-cursor fallback, maxPages cap, and out-of-range databaseIndex guard.
apps/sim/connectors/google-calendar/google-calendar.ts Multi-calendar pagination and externalId namespacing implemented correctly. getDocument derives isMultiCalendar from the stored externalId separator (not current config count), addressing the previously flagged downgrade-scenario duplicate.
apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.ts New ConfigFieldValue/ConfigFieldMap types, coerceForField, emptyValue, isValuePopulated, and isFieldPopulated are all well-typed and handle string/array duality correctly.
apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx Order-insensitive valuesEqual via Set comparison and multi-field initialSourceConfig coercion are both correctly implemented.

Reviews (5): Last reviewed commit: "fix(gmail-connector): always wrap OR-con..." | Re-trigger Greptile

Comment thread apps/sim/connectors/google-calendar/google-calendar.ts
Comment thread apps/sim/connectors/slack/slack.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e6d8ccd. Configure here.

… skip, valuesEqual order

- google-calendar getDocument: derive isMultiCalendar from the externalId's
  `:` separator instead of the current config count. Prevents duplicates when
  a user downgrades from multi to single calendar — previously the returned
  doc lost its `calendarId:` prefix and was treated as a new row by the sync
  engine, orphaning the original.
- slack listDocuments: throw on unresolvable channel instead of silently
  skipping. Matches MS Teams behaviour. Silent skip would let the sync
  engine orphan-delete the previously indexed channel content if a bot was
  removed or a channel was archived/renamed mid-life.
- edit-connector-modal valuesEqual: order-insensitive comparison for multi-
  select arrays via Set membership. Multi-select UI doesn't guarantee
  insertion order matches the server-returned order, so `["A","B"]` vs
  `["B","A"]` would otherwise flag false unsaved changes.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR adds multi-select support to 8 KB connectors (Jira, Confluence, Slack, MS Teams, GCal, Gmail, Notion, Linear) via a new multi?: boolean flag on ConnectorConfigField, and fixes two pre-existing bugs: Slack dropping bot/app messages that carry content in attachments or Block Kit blocks, and the Confluence selector returning space.id instead of space.key.

  • Multi-select infrastructure: A new parseMultiValue utility normalizes any string | string[] | undefined to a deduplicated string[]. Each connector's listDocuments, getDocument, and validateConfig are updated to call it. The UI hook exports ConfigFieldValue = string | string[] and the modals handle the new array shape throughout.
  • Slack bot-message fix: extractMessageContent and walkBlockText are added to pull text from legacy attachments, Block Kit blocks, and nested attachment.blocks; the contentHash is bumped to slack-v2 to force a one-time re-embed for existing channels.
  • Backward compatibility: Single-value configs produce byte-identical externalId values, except Google Calendar multi-calendar mode which namespaces IDs as calendarId:eventId; legacy single-calendar rows carry no separator and are correctly handled in getDocument.

Confidence Score: 3/5

The core multi-select infrastructure and most connectors are solid, but two defects in Microsoft Teams and Google Calendar warrant fixes before merge.

The Microsoft Teams listDocuments loop throws when any one channel is not found rather than skipping it — adding a second channel that later becomes inaccessible will permanently break sync for all other channels in that connector. In Google Calendar, getDocument computes isMultiCalendar from the current config length rather than from the stored externalId format; a user who removes one of two configured calendars will get a format mismatch that causes the sync engine to emit duplicate documents instead of updating existing ones.

apps/sim/connectors/microsoft-teams/microsoft-teams.ts (throw vs. skip in the channel loop) and apps/sim/connectors/google-calendar/google-calendar.ts (isMultiCalendar derivation in getDocument)

Important Files Changed

Filename Overview
apps/sim/connectors/microsoft-teams/microsoft-teams.ts Multi-channel support added; however listDocuments throws (rather than skips) when any one of the selected channels is not found, causing the entire sync to fail if a channel becomes inaccessible post-configuration.
apps/sim/connectors/google-calendar/google-calendar.ts Multi-calendar pagination with compound cursors is well-designed. getDocument correctly parses namespaced externalIds, but isMultiCalendar is derived from the current config rather than the stored externalId format — transitioning from 2 calendars to 1 could produce a mismatched externalId and create duplicate documents.
apps/sim/connectors/slack/slack.ts Adds multi-channel support and rich content extraction (attachments, Block Kit). Multi-channel loop correctly skips missing channels.
apps/sim/connectors/notion/notion.ts Multi-database pagination with compound cursors handles single-DB (bare cursor) and multi-DB (JSON cursor) cases correctly; backward-compatible with legacy single-DB cursors.
apps/sim/connectors/confluence/confluence.ts Multi-space CQL routing is correct; single-space path preserved for backward compat; validation correctly checks all keys are found in API response.
apps/sim/hooks/selectors/providers/confluence/selectors.ts Bug fix: selector now correctly emits space.key instead of space.id so the value persisted to sourceConfig matches what Confluence CQL expects.
apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.ts Introduces `ConfigFieldValue = string
apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx Adds valuesEqual for correct dirty-state detection across string/array/CSV forms; initial config loading correctly normalizes multi-fields to string[].

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    UI["UI: multi-select Combobox\n(ConnectorSelectorField)"] -->|"string[]"| Hook
    UI2["UI: CSV Input\n(short-input + multi:true)"] -->|"string (CSV)"| Hook
    Hook["useConnectorConfigFields\nresolveSourceConfig()"] -->|"Record<string, unknown>\n(string | string[])"| Modal
    Modal["Add/Edit Connector Modal\nsubmits sourceConfig"] --> Connector
    subgraph Connector["Connector listDocuments / validateConfig"]
        PMV["parseMultiValue(value)\n→ string[]"]
        PMV --> Single{"length === 1?"}
        Single -->|Yes| LegacyPath["Legacy single-value path\n(same externalId as before)"]
        Single -->|No| MultiPath["Multi-value path\n(fan-out or IN clause)"]
    end
    subgraph GCal["GCal getDocument"]
        ExId["externalId.indexOf(':')"]
        ExId -->|"-1 (no colon)"| BareId["eventId = externalId\ncalendarId = config[0]"]
        ExId -->|">=0 (namespaced)"| SplitId["calendarId:eventId split"]
    end
    Connector --> SyncEngine["Sync Engine\n(externalId + contentHash dedupe)"]
Loading

Comments Outside Diff (1)

  1. apps/sim/connectors/microsoft-teams/microsoft-teams.ts, line 862-866 (link)

    P1 Throw on channel-not-found breaks entire multi-channel sync

    In the updated listDocuments loop, if any one of the selected channels resolves to null the function throws, abandoning all other channels in the batch. The Slack connector (new code, same PR) uses continue instead — a more resilient pattern for multi-value syncs. Once a Teams channel is deleted or the bot loses access to it post-configuration, the sync for every other channel in the same connector will fail permanently until the config is edited.

    The previous single-channel behavior (throw if the single channel is missing) was acceptable because a missing channel meant the whole connector was broken. With multi-channel, the same throw now silently kills documents from all valid channels.

Reviews (2): Last reviewed commit: "fix(kb-connectors): GCal externalId on c..." | Re-trigger Greptile

Comment thread apps/sim/connectors/google-calendar/google-calendar.ts
…or consistency

Behavior unchanged — isValuePopulated('') and isValuePopulated([]) both return
false — but reading the field-typed fallback inline matches the convention
used elsewhere in the hook (coerceForField, handleFieldChange, resolveSourceConfig).
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/connectors/linear/linear.ts
…d teams

When the team selector is in multi-select mode, the basic-mode projects
dropdown was passing only the first team ID into the linear.projects
selector context (via readFirst in resolveDepValue), so projects from other
selected teams were invisible.

resolveDepValue now joins multi-value parents into a CSV string so dependent
selectors receive every selected parent ID. The /api/tools/linear/projects
route splits the CSV teamId, fetches projects from each team in parallel,
and dedupes by project ID. Single-team configs pass through unchanged
(`split(",")` on a bare ID yields a one-element array).

The AND-of-filters semantics in buildIssuesQuery is intentional and matches
standard GraphQL filter behavior — a user filtering on teams [A,B] and
projects [X,Y] gets issues in (A or B) AND (X or Y). With this fix the
project dropdown now shows every project under any selected team so the
user can compose the right project set.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/connectors/gmail/gmail.ts
…t unwrapped ones

The previous check `!/^\(.*\)$/.test(trimmedCustom)` was supposed to avoid
double-wrapping an already-parenthesized expression, but it false-positives
on inputs like `(from:alice) OR (from:bob)` where the parens don't bracket
the whole string. Those would skip wrapping and the top-level OR would bind
across the preceding label / category / date filters instead of the custom
clause.

Always wrap when an OR is present — double-parens are a no-op in Gmail
search syntax, so `((from:a OR from:b))` parses the same as `(from:a OR
from:b)`. Simpler than walking parens depth and provably safe.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 84c3159. Configure here.

@waleedlatif1 waleedlatif1 merged commit e0551b3 into staging May 22, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/jira-kb-multi-project-select-v2 branch May 22, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant