Skip to content

feat: Jira Cloud integration (connection, inbound webhook, pull-sync + settings UI)#5919

Open
zerox9dev wants to merge 15 commits into
multica-ai:mainfrom
zerox9dev:feat/jira-integration
Open

feat: Jira Cloud integration (connection, inbound webhook, pull-sync + settings UI)#5919
zerox9dev wants to merge 15 commits into
multica-ai:mainfrom
zerox9dev:feat/jira-integration

Conversation

@zerox9dev

Copy link
Copy Markdown

Addresses #1634.

Summary

Adds Jira Cloud integration so issues assigned in Jira surface as Multica issues. Built on the existing VCS-integration pattern (per-workspace, API-token auth, secretbox-encrypted secrets — no Atlassian OAuth app required, works for self-hosters out of the box).

Two ingest paths, so it works whether or not you have Jira admin rights:

  • Pull (no admin needed): a "Sync now" action + optional per-connection JQL (default assignee = currentUser()) fetches issues via the Jira REST API and creates/syncs them. No webhook, no tunnel, no site-admin.
  • Push (instant, optional): an inbound webhook POST /api/webhooks/jira/{connectionId} verified by a per-connection secret (Jira Cloud doesn't sign webhooks) creates/syncs on jira:issue_created / jira:issue_updated.

What's included

  • Schema (migrations 224–229, no FKs, CREATE INDEX CONCURRENTLY one-per-file): jira_connection (incl. optional jql) + jira_issue_link.
  • Backend: connection CRUD, inbound webhook, pull-sync endpoint (POST .../jira/connections/{id}/sync{created, updated, total}), Jira REST client using the enhanced /rest/api/3/search/jql (the offset-based /rest/api/2/search was removed on Cloud). Webhook and pull share one create-or-sync function.
  • Frontend: Settings → Integrations → Jira tab — connect/disconnect, optional JQL, one-time webhook secret with copy, and "Sync now".
  • i18n: jira strings in en, zh-Hans, ko, ja.
  • Tests: Jira client (httptest), webhook + sync handlers (DB-backed), Jira settings tab.

Auth model — steer welcome

v1 uses a per-workspace Jira Cloud API token (email + API token + base URL, encrypted). Atlassian Connect / OAuth (3LO) would be cleaner but needs a hosted Atlassian app; I'd add it later rather than block this. Happy to adjust.

Not in this PR (follow-ups)

  • Outbound sync (Multica status change → Jira transition/comment) + configurable status mapping.
  • Injecting the Jira issue key into the agent's prompt context.
  • Periodic scheduled poll (the manual "Sync now" is here; a scheduler JobSpec is the clean way to automate it).

Verification

  • go build/vet clean; Jira client + handler tests pass (DB-backed).
  • Migrations apply cleanly.
  • pnpm typecheck 6/6; locale parity + Jira tab tests pass.
  • Manually verified end-to-end against a real Jira Cloud site (pull-sync) and a fake webhook (push).

zerox9dev added 15 commits July 24, 2026 21:45
Tables for workspace-scoped Jira connections (API-token auth, secrets
stored as secretbox ciphertext) and Jira->Multica issue links. No FKs or
cascades per migration rules; cleanup happens in application code.
Concurrent secondary indexes live in single-statement migrations 225-227.
CRUD/upsert queries for jira_connection and jira_issue_link mirroring
vcs.sql, plus a narrow SyncIssueFromJira title/description update used by
the inbound webhook. Generated with sqlc v1.31.1.
Webhook secret verification (constant-time shared secret via header or
query param, since Jira webhooks carry no native signature), normalized
jira:issue_created / jira:issue_updated parsing with assignee-change
detection and ADF description flattening, and a minimal REST client
(myself + issue fetch) behind a Client seam handlers can mock.
Extends the issue_origin_type_check constraint (pattern of migrations
060/111/131/149) so the inbound Jira webhook can stamp mirrored issues
with origin_type='jira' + origin_id=<jira_connection.id>.
Mirrors the VCS integration shape:
- handler/jira.go: workspace-scoped connection list/connect/get/delete;
  credentials validated live via the jira.Client seam, API token and
  minted webhook secret stored as MULTICA_JIRA_SECRET_KEY secretbox
  ciphertext, one-time plaintext webhook secret returned at connect.
- handler/jira_webhook.go: POST /api/webhooks/jira/{connectionId} with
  10MiB body cap, refuse-if-unconfigured, constant-time per-connection
  secret verification, and create-or-sync of the mirrored Multica issue
  (IssueService.Create on first delivery, narrow title/description sync
  after; thin payloads enriched via the REST client).
- router.go: public webhook route, member-visible reads, admin-gated
  connect/disconnect, and the MULTICA_JIRA_SECRET_KEY secretbox wiring.
- DB-backed webhook tests with a mocked Jira client (no real network).
SearchIssues pages GET /rest/api/2/search until the caller's maxResults
(hard-capped at 100) and returns the same minimal Issue shape the webhook
enrichment path uses, plus the assignee identity. 400 responses map to a
new ErrBadRequest so a bad JQL surfaces as a user-fixable error. Powers
the pull-based sync for accounts that cannot register Jira webhooks.
Migration 229 adds a nullable jql column to jira_connection (no FK, no
index). UpsertJiraConnection persists it, the connect endpoint accepts an
optional jql field, and connection responses expose it so the settings UI
can show what a sync will pull. Empty means the pull path's default
'assignee = currentUser()'.
…sync path

syncJiraIssue now returns a created/updated/skipped outcome so it serves
as the single per-issue mirror path for both the webhook and the new
POST /workspaces/{id}/jira/connections/{connectionId}/sync endpoint. The
endpoint searches the site with the connection's stored JQL (default
'assignee = currentUser()'), mirrors up to 100 results, and returns a
{created, updated, total} summary. Admin-gated like the other Jira
mutations; lets users without Jira admin rights import their issues
without registering a webhook.
Each connection card gets a Sync now button that hits the pull-based sync
endpoint, spins while in flight, toasts the {created, updated} summary and
invalidates the workspace issue queries so pulled issues appear at once.
The connect form gains an optional JQL field (default shown as the
placeholder). Strings added to en, zh-Hans, ko and ja.
… Cloud)

Jira Cloud removed the offset-based /rest/api/2/search endpoint; switch
SearchIssues to the enhanced JQL endpoint /rest/api/3/search/jql with
nextPageToken cursor pagination. Fixes 502 on manual sync against real
Jira Cloud sites.
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@zerox9dev is attempting to deploy a commit to the IndexLabs Team on Vercel.

A member of the Team first needs to authorize it.

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