/claim #2281

Summary

Adds a new Jira Service Management (JSM) connector that indexes service desk tickets — service requests, incidents, problems, and changes — with full SLA enrichment, dynamic field discovery, and complete frontend support.

Problem

Jira Software and Jira Service Management are distinct products with different data models. JSM tickets have additional metadata that the existing Jira connector does not capture:

  • Request type (e.g. “VPN Access”, “Hardware Request”, “New Employee Onboarding”)
  • SLA time-to-first-response with breach tracking
  • SLA time-to-resolution with breach tracking
  • JSM issue types (Service Request, Incident, Problem, Change) vs. standard Jira types

Without a dedicated JSM connector, indexing a service desk project with the existing Jira connector loses all SLA and request-type context — making it harder for support agents and end-users to search and understand ticket history.

Design

Inheritance from JiraConnector

JiraServiceManagementConnector extends JiraConnector directly, inheriting all production-tested capabilities for free:

Capability Inherited from JiraConnector
Checkpoint-based incremental sync
Slim document support (pruning)
Permission sync (EE)
JQL flexibility
Comment indexing
Hierarchy nodes (epics/projects)
Error handling / credential validation

The connector only overrides what JSM needs: _get_jql_query (adds JSM issue type filter) and adds _enrich_document_with_jsm_metadata (adds JSM-specific fields to indexed documents).

Dynamic Custom Field ID Discovery

This is the critical design decision that differentiates this implementation.

Previous attempts hard-coded JSM custom field IDs like customfield_10010, customfield_10020, customfield_10030. This is fundamentally broken: Jira custom field IDs are assigned sequentially at instance creation time and differ between instances. A field that is customfield_10010 on one instance may be customfield_20045 on another.

This connector discovers field IDs dynamically via jira_client.fields() (the standard JIRA Python library API, wrapping /rest/api/2/field), matching by field name patterns:

_FIELD_NAME_REQUEST_TYPE = ("request type", "customer request type")
_FIELD_NAME_TIME_TO_FIRST_RESPONSE = ("time to first response",)
_FIELD_NAME_TIME_TO_RESOLUTION = ("time to resolution",)

The result is cached per connector instance after the first call — zero overhead on subsequent syncs.

def _discover_jsm_fields(self) -> dict[str, str]:
if self._jsm_field_map is not None:
return self._jsm_field_map # cached
all_fields = self.jira_client.fields() # single API call
for field in all_fields:
field_name = (field.get("name") or "").lower()
if any(pat in field_name for pat in _FIELD_NAME_REQUEST_TYPE):
field_map["request_type"] = field.get("id")
# ... etc
self._jsm_field_map = field_map # cache
return field_map

If field discovery fails (permissions issue, older Jira version), the connector logs a warning and continues — SLA enrichment is skipped gracefully, core ticket indexing still works.

SLA Field Format Handling

JSM SLA fields have different structures across Cloud and Server/DC:

Cloud format (nested dict):

{
"ongoingCycle": {
"breached": false,
"remainingTime": {"friendly": "2h 30m"}
}
}

Server/DC format (plain string): "3h remaining"

The _extract_sla_display() helper handles both formats, plus completed cycles (for resolved tickets), with breach flag propagation:

def _extract_sla_display(sla_value: Any) -> tuple[str | None, bool]:
# Cloud: ongoingCycle
if isinstance(sla_value, dict):
ongoing = sla_value.get("ongoingCycle")
if isinstance(ongoing, dict):
breached = ongoing.get("breached", False)
remaining = ongoing.get("remainingTime", {})
return remaining.get("friendly"), breached
# Cloud: completedCycles (resolved tickets)
completed = sla_value.get("completedCycles")
if completed:
last = completed[-1]
return last.get("elapsedTime", {}).get("friendly"), last.get("breached", False)
# Server: plain string
if isinstance(sla_value, str):
return sla_value, False
return None, False

JQL Filtering

When no custom JQL is provided, the connector automatically filters to JSM issue types:

(project = "SD" AND updated >= "2025-01-01 00:00" AND updated <= "2026-01-01 00:00")
AND issuetype in ("Service Request", "Service Request with Approvals", "Incident", "Problem", "Change")

When the user provides a custom JQL query, it is passed through unchanged (no filter injection) — giving full flexibility for advanced use cases.

Files Changed

Backend — New Files

backend/onyx/connectors/jira_service_management/__init__.py Package init.

backend/onyx/connectors/jira_service_management/connector.py Main connector implementation (~270 lines):

  • JiraServiceManagementConnector class extending JiraConnector
  • _discover_jsm_fields() — dynamic field discovery with caching
  • _enrich_document_with_jsm_metadata() — adds request type + SLA to indexed docs
  • _extract_sla_display() — Cloud/Server SLA format handling with breach detection
  • _extract_request_type_name() — handles Cloud dict, Server string, resource object
  • _get_jql_query() override — injects JSM issue type filter
  • process_jsm_issue() — convenience wrapper composing base + enrichment

backend/tests/daily/connectors/jira_service_management/__init__.py Test package init.

backend/tests/daily/connectors/jira_service_management/test_jsm_connector.py 25 unit tests across 5 test classes:

Class Tests
TestJSMConnectorInitialization 6 tests: init, inheritance, service_desk_id, optional fields, credential loading, DocumentSource enum
TestDynamicFieldDiscovery 4 tests: success, failure graceful, caching (fields() called once), partial fields
TestJSMMetadataEnrichment 4 tests: request type enrichment, full SLA extraction, missing SLA graceful, source override
TestJQLGeneration 3 tests: JSM types injected, with project key, custom JQL passthrough
TestSLAHelpers 8 tests: ongoing cycle, breached, completed cycle, string format, None, request type dict/string/None

Backend — Modified Files

backend/onyx/configs/constants.py

  • Added JIRA_SERVICE_MANAGEMENT = "jira_service_management" to DocumentSource enum
  • Added human-readable description: "jira service management data (service requests, incidents, problems, changes, SLAs)"

backend/onyx/connectors/registry.py

  • Added DocumentSource.JIRA_SERVICE_MANAGEMENTJiraServiceManagementConnector mapping

Frontend — Modified Files

web/src/lib/sources.ts

  • Added jira_service_management entry with Jira icon, “Jira Service Management” display name, TicketingAndTaskManagement category

web/src/lib/connectors/connectors.tsx

  • Added full JSM connector configuration form with fields:
    • Jira Base URL (required)
    • Service Desk ID (optional — filter to specific service desk)
    • Scoped token checkbox
    • Indexing scope tabs: Everything / Project / Custom JQL
    • Comment email blacklist

web/src/lib/connectors/credentials.ts

  • Added JiraServiceManagementCredentialJson interface (jira_user_email, jira_api_token)
  • Added template entry in credentialTemplates

Testing Setup

For end-to-end validation against a real JSM instance:

# Set environment variables
export JIRA_BASE_URL="https://your-domain.atlassian.net"
export JIRA_USER_EMAIL="your-email@domain.com"
export JIRA_API_TOKEN="your-api-token"
export JSM_PROJECT_KEY="SD" # your JSM project key
# Run the daily connector test
cd backend
python -m pytest tests/daily/connectors/jira_service_management/ -v
# Or run unit tests (no Jira instance needed)
python -m pytest tests/daily/connectors/jira_service_management/test_jsm_connector.py -v

Unit test output (no Jira instance required):

tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_connector_initialization PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_inherits_jira_connector PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_service_desk_id_stored PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_service_desk_id_optional PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_credential_loading PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestJSMConnectorInitialization::test_document_source_enum_exists PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestDynamicFieldDiscovery::test_dynamic_field_discovery_success PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestDynamicFieldDiscovery::test_dynamic_field_discovery_failure_graceful PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestDynamicFieldDiscovery::test_dynamic_field_discovery_cached PASSED
tests/daily/connectors/jira_service_management/test_jsm_connector.py::TestDynamicFieldDiscovery::test_discovery_partial_fields PASSED
... (25/25 PASSED)

Comparison with Competing PRs

Feature This PR promisingcoder #9536 Raw9982 #9121 zhaog100 #9437
Inherits JiraConnector ❌ (copy-paste) ❌ (custom client)
Dynamic field IDs ❌ hard-coded ❌ hard-coded ❌ hard-coded
Frontend (connectors.tsx)
Frontend (sources.ts)
credentials.ts interface
Unit tests ✅ 25 tests ✅ 3 tests
Cloud + Server SLA formats ✅ (partial)
Breach detection
Graceful field discovery fail
Field discovery caching N/A N/A N/A

Checklist

  • Extends existing JiraConnector (no code duplication)
  • Dynamic custom field ID discovery (no hard-coded customfield_XXXXX)
  • Handles Cloud and Server/DC SLA formats
  • Graceful degradation when JSM fields unavailable
  • Full type annotations
  • DocumentSource enum updated
  • registry.py updated
  • Frontend: sources.ts, connectors.tsx, credentials.ts
  • Tests: 25 unit tests across 5 test classes
  • Follows onyx connector README requirements

Summary by cubic

Adds a Jira Service Management (jira_service_management) connector to index service desk tickets with request type and SLA details via dynamic field discovery. Enrichment runs through the Jira connector’s _enrich_document hook so every synced ticket includes JSM metadata; addresses #2281.

  • New Features

    • New JiraServiceManagementConnector extends the Jira connector and enriches documents via _enrich_document; filters to JSM issue types.
    • Dynamically discovers field IDs for request type, time-to-first-response, and time-to-resolution; supports Cloud and Server/DC; better error logging and graceful fallback.
    • SLA parsing adds friendly times and sla_breached; returns “Breached” when breached with no display.
    • JQL: auto-adds JSM issue type filter; when service_desk_id is set without project_key or custom JQL, injects project = <service_desk_id>; with custom JQL + service_desk_id, still appends the JSM filter; otherwise passthrough.
    • Full UI support: new source entry, config form (Jira base URL, optional service desk ID, indexing scope tabs, comment email blacklist), and credential template reuse.
  • Migration

    • Create credentials with jira_user_email and jira_api_token.
    • Add the new connector in the UI and choose scope (Everything, Project, or Custom JQL).
    • No changes needed for existing Jira connector setups.

Written for commit 902772e7d5cc63b2421920d55a0c292ed2f333d0. Summary will update on new commits.

Claim

Total prize pool $250
Total paid $0
Status Pending
Submitted March 26, 2026
Last updated March 26, 2026

Contributors

CH

CharlesWong

@CharlesWong

100%

Sponsors

ON

Onyx (YC W24)

@onyx-dot-app

$250