Jira connector¶
Import Jira issues into a Sphinx-Needs needs.json file, and create Jira
issues from needs while tracking their status.
Note
The jira connector requires an active ubConnect license. See
License configuration for activation instructions. The connector
accepts the license via UBCONNECT_LICENSE_KEY /
UBCONNECT_LICENSE_USER environment variables, the
ubconnect.toml license file, or the --license-key /
--license-user CLI options.
Quick start¶
Add a [ubconnect.from_jira] section to your ubproject.toml:
[ubconnect.from_jira]
url = "https://example.atlassian.net"
username = "alice@example.com"
password = "your-api-token"
project_key = "PROJ"
outdir = "./generated"
Then run:
$ ubconnect jira from-jira --config ubproject.toml
This produces ./generated/needs.json.
Authentication¶
The connector supports two auth modes via auth_type:
"basic"(default): Cloud uses the Atlassian email plus API token; Server / Data Center uses username plus password.usernameis required."bearer": Jira Data Center personal access token (PAT). Setauth_type = "bearer"and put the PAT inpassword;usernameis ignored.
Type mapping defaults¶
Standard Jira issue types map by default:
Jira type |
Need type |
|---|---|
Epic |
|
Story |
|
Task |
|
Bug |
|
Sub-task |
|
Anything else falls back to fallback_type (default "req") and
emits one WARN per unique unmapped type. Override per project:
[ubconnect.from_jira.type_mapping]
"Test Case" = "spec" # Xray / Zephyr plugin
"Spike" = "dec"
Field mapping defaults¶
Three Jira fields map by default:
Jira field |
Need field |
|---|---|
|
|
|
|
|
|
Add more by extending [ubconnect.from_jira.mapping]:
[ubconnect.from_jira.mapping]
priority = "priority"
assignee = "assignee"
labels = "labels"
Audit extras¶
Every imported need carries:
jira_key(always): the original Jira issue key, e.g."PROJ-12".jira_url(always): a deeplink built fromurland the issue key.jira_parent_key(conditional): set when the issue has a parent, always present on Sub-tasks. For Story-to-Epic relationships, present only on modern Jira (Cloud Next-gen, Cloud Company-managed, Data Center / Server 8.x and newer). On older Cloud classic projects, Epic Link lives in a custom field; extend[ubconnect.from_jira.mapping]with the custom field id to capture it.
Link mapping¶
The connector ships no built-in link defaults because Jira link type
names vary widely between teams. The short form (key on the left,
target on the right) is intended for symmetric Jira link types where
inward and outward carry the same label (for example
“Relates”). For asymmetric types (Blocks, Cloners, Duplicate), use the
long form (table) so both directions resolve correctly. Use the
following starter preset:
# Always available on every Jira instance (default Atlassian link types)
[ubconnect.from_jira.link_mapping]
"is blocked by" = "blocks" # Blocks link, inward direction
"is cloned by" = "links" # Cloners link, inward direction
"is duplicated by" = "links" # Duplicate link, inward direction
"relates to" = "links" # Relates link (symmetric)
The block below is common but project-dependent; only enable it if your Jira instance actually defines these link types. Long form is recommended for asymmetric link types so both inward and outward labels resolve correctly:
# [ubconnect.from_jira.link_mapping.blocks]
# inward = "is blocked by"
# outward = "blocks"
# target = "blocks"
#
# [ubconnect.from_jira.link_mapping.cloned]
# inward = "is cloned by"
# outward = "clones"
# target = "links"
#
# [ubconnect.from_jira.link_mapping.duplicate]
# inward = "is duplicated by"
# outward = "duplicates"
# target = "links"
For full control, use the long form:
[ubconnect.from_jira.link_mapping.implements]
inward = "is implemented by"
outward = "implements"
target = "implements"
Markup conversion¶
The connector translates Jira description bodies to RST automatically:
Server / Data Center wiki markup to RST.
Cloud ADF (Atlassian Document Format) to RST.
The converter covers the common surface (paragraphs, headings, lists, code blocks, basic inline marks, links). Advanced ADF nodes (tables, panels, media) emit placeholders. Full-fidelity conversion via Pandoc is tracked separately.
Covered constructs (full conversion):
Paragraphs, headings, hard breaks, horizontal rules.
Bullet and ordered lists, list items.
Code blocks (with language hint).
Blockquotes.
Inline marks: bold, italic, code, links, strikethrough.
Placeholder constructs (rendered as RST notes / minimal directives):
Tables (single-row
.. list-table::placeholder; full conversion tracked separately).Mentions, emoji, panels, expand / nested expand, media.
Unsupported constructs are aggregated and logged as a single WARN per unique node type at end of run. The original text content survives the conversion in all known cases; advanced layout features may render as plain text.
JQL filtering¶
For complex selection, use jql:
[ubconnect.from_jira]
project_key = "PROJ"
jql = 'fixVersion = "1.4.0" AND status != "Done"'
When constructing JQL programmatically (CI templates, scripted config generation), sanitize external inputs (branch names, ticket IDs) before splicing into the query. The connector does not escape JQL values.
Status normalization¶
Jira status values are free-form workflow states (“To Do”, “In Progress”,
“Won’t Fix”, “Done”); the default mapping is direct passthrough. To pin
status to a single value across imports use static_fields:
[ubconnect.from_jira.static_fields]
status = "open"
Visualizing imported issues¶
Once ubconnect jira from-jira has produced needs.json, dashboard
directives in your Sphinx project can render the imported needs.
Starting points:
A traceability table with a clickable deeplink back to Jira (the
jira_url extra is added by the connector to every imported need):
.. needtable::
:filter: "jira_key" in extras
:columns: id, type, title, jira_key, status
:style: table
A flow diagram over the imported issues and their links:
.. needflow::
:filter: "jira_key" in extras
:show_link_names:
:alt: Imported Jira issues and their relations
For multi-component layouts (needpie / needbar / needgantt), reusable filter definitions, and color-scheme conventions, see the Creating Dashboards tutorial.
Configuration reference¶
All available fields:
Field |
Default |
Description |
|---|---|---|
|
required |
Jira base URL. |
|
required for basic auth |
Email (Cloud) or username (Server / Data Center). |
|
required |
API token, password, or personal access token. |
|
|
One of |
|
|
Disable for self-signed certificates. |
|
unset |
HTTP timeout (connect plus read). |
|
XOR with |
Project key, e.g. |
|
XOR with |
Numeric project id. |
|
unset |
Free-form JQL filter. |
|
unset |
Whitelist of Jira issue types; XOR with |
|
unset |
Blacklist of Jira issue types; XOR with |
|
3 defaults |
Jira field to Need field map. |
|
5 defaults |
Jira issue type to Need type map. |
|
|
Used for unmapped Jira types. |
|
|
See above. |
|
|
Values applied unconditionally to every emitted need. |
|
|
Prepended to |
|
|
Per-issue error policy. |
|
|
Capped at the Jira API limit of 100. |
|
|
Retries on HTTP 429 and 5xx responses. |
|
unset |
HTTP proxy URL. |
Creating Jira issues from needs¶
ubconnect jira to-jira creates one Jira issue per selected need. Content
flows one way: need content is written into Jira, and only the issue status
is read back. Updating an already-created issue when its need changes is out
of scope, as is deciding which needs are new — pair this with ubc diff if
you need change detection.
Quick start¶
[ubconnect.to_jira]
url = "https://jira.example.com"
username = "alice@example.com"
password = "your-api-token"
project = "SWDEV"
issue_type = "Story"
needsjson_path = "./generated/needs.json"
select = 'type == "req" and status == "approved"'
# Where a Jira user can browse the exported need
need_url = "https://ubtrace.example.com/org/proj/latest/needs/{{ need.id }}"
[ubconnect.to_jira.template]
summary = "{{ need.id }} — {{ need.title }}"
description = """
{{ need.content }}
Parent: {{ links.parent_needs[0].id }} — {{ links.parent_needs[0].title }}
Browse the requirement: {{ need_url }}
"""
[ubconnect.to_jira.linked_needs]
follow = ["parent_needs"]
fields = ["id", "title", "status"]
[ubconnect.to_jira.status_tracking]
enabled = true
map = { "To Do" = "open", "In Progress" = "in_progress", "Done" = "done" }
Preview before you write anything:
$ ubconnect jira to-jira --config ubproject.toml --dry-run
Then run the export:
$ ubconnect jira to-jira --config ubproject.toml
REQ_001: created SWDEV-1
REQ_002: created SWDEV-2
created 2, skipped 0, failed 0
Authentication is identical to the import direction — see Authentication.
Selecting needs¶
select is a filter expression over need fields, including your own extra
options. It admits comparisons (==, !=, <, in, …), boolean
operators, names, and literals — nothing else, so a configuration file cannot
smuggle code into the export run. Omit select to export every need.
Templates¶
Templates are MiniJinja and see three context entries:
need— the exported need, core fields plus extras.links.<link_type>— needs reachable through the link types named infollow, projected ontofields. Traversal is one hop only.need_url— the rendered back-link for this need.
Rendering is strict: a template referencing a field the need does not carry fails that need and is reported, rather than silently producing an issue with an empty field.
Writing to other Jira fields¶
Any key beyond summary and description is sent to the Jira field of
the same name, so customfield_10101 = "{{ need.id }}" populates that
custom field:
[ubconnect.to_jira.template]
summary = "{{ need.id }} — {{ need.title }}"
description = "{{ need.content }}"
customfield_10101 = "{{ need.id }}" # a text field
duedate = "{{ need.due }}" # a date, as YYYY-MM-DD
A template always renders to text. The value is sent to Jira as a string,
so this reaches only fields that accept one. Jira rejects the rest, and
because the rejection happens per need, the run reports that need as failed
and carries on (or aborts, under on_error = "fail"):
Jira field type |
Reachable |
Notes |
|---|---|---|
Text (single-line, paragraph) |
yes |
The ordinary case. |
Date / datetime |
yes |
Rendered text must already be in Jira’s format, e.g. |
URL |
yes |
Sent as a plain string. |
Number |
no |
Jira answers |
Labels, multi-select, components, versions |
no |
Jira expects an array, e.g. |
Priority, assignee, reporter, user and option pickers |
no |
Jira expects an object, e.g. |
There is no escape hatch for the unreachable types: a template cannot emit a
JSON array or object, so populating them needs a follow-up step against the
Jira API. labels in particular is worth avoiding — the connector already
writes the ubconnect-need:<need id> tracking label there, and a template
key of that name replaces it (Jira then rejects the string, so the export
fails rather than quietly losing the mapping).
The target field must also be on the project’s create screen for the
configured issue type. A field that exists but is not on that screen is
refused with Field '<id>' cannot be set. It is not on the appropriate
screen, or unknown.
Back-links and duplicate prevention¶
Every created issue carries the rendered need_url twice: as a proper Jira
remote link, and wherever your description template places it.
For a first-class, queryable reference, point need_field at a Jira field
provisioned for this purpose:
[ubconnect.to_jira]
need_field = "customfield_10101"
need_field_value = "id" # or "url"
With "id" the field holds the bare need id, which is what makes the issue
findable by JQL and lets Jira compute and render a link from the id via its
Permalink feature. With "url" the field holds the fully rendered
need_url instead. When need_field is unset the key is omitted from the
create payload entirely, so no project is forced to provision a custom field.
Both values are strings, so need_field must name a text (or URL) field
that is on the create screen for the configured issue type — the same
constraint as any other field written from a template, see
Writing to other Jira fields.
The need-to-issue mapping is recorded in state_file (default
.ubconnect/jira_state.json, relative to the config file). It is flushed
after every created issue, so a run interrupted halfway does not re-create the
issues it already made.
If the state file is lost, ubConnect rebuilds it from Jira — see Reconciliation below.
Reconciliation¶
Because every created issue carries the exported need’s reference (in
need_field, or in the ubconnect-need:<need id> label when no field is
configured), the mapping can be read back out of Jira. That is what makes a
lost state file recoverable, and what makes stateless CI possible.
[ubconnect.to_jira]
reconcile = "missing" # "missing" (default) | "always" | "never"
Policy |
Behaviour |
|---|---|
|
Query Jira before exporting whenever the state file holds no mapping. Repairs a lost state file. This is the default. |
|
Query Jira before every export. Use this when the pipeline keeps no state between runs at all. |
|
Never query. The state file is the only source of truth. |
--reconcile on the command line raises the policy to "always" for one
run:
$ ubconnect jira to-jira --config ubproject.toml --reconcile
reconciled 2 need-to-issue pair(s) from Jira
REQ_001: already exported as SWDEV-1
REQ_002: already exported as SWDEV-2
created 0, skipped 2, failed 0
For stateless CI, set reconcile = "always" and provision need_field.
The state file then becomes a per-run cache rather than something you must
commit.
Note
The default query scans the configured project
(project = "<project>") and matches the need reference client-side.
Server-side filtering is deliberately avoided because JQL custom-field
syntax differs between Jira deployments (cf[10101] versus the field
name), and getting it wrong would silently recover nothing. On a large
project, narrow the scan with reconcile_jql:
[ubconnect.to_jira]
reconcile_jql = 'project = "SWDEV" AND issuetype = "Story" AND created > -90d'
An issue whose reference matches no loaded need is ignored. If two issues claim the same need, the first one wins and a warning names both, so the export cannot silently flip between them from run to run.
Tracking Jira status¶
ubconnect jira refresh-status reads the current status of every mapped
issue and writes a needs.json into outdir, ready for needimport:
$ ubconnect jira refresh-status --config ubproject.toml
REQ_001: in_progress
REQ_002: open
Each emitted need carries its status translated through
status_tracking.map (unmapped Jira status names pass through unchanged)
and a jira_key extra option holding the issue key.
Export configuration reference¶
Field |
Default |
Description |
|---|---|---|
|
required |
Jira base URL. |
|
see Authentication |
Same credential shape as |
|
required |
Target Jira project key. |
|
required |
Target issue type ( |
|
required |
Source |
|
required |
Back-link template rendered per need. |
|
required |
Issue summary template. |
|
|
Issue description template. |
|
unset |
Any further key writes to the Jira field of that name. Text-shaped fields only; see Writing to other Jira fields. |
|
unset |
Filter expression; unset exports every need. |
|
|
Link types traversed for the template context. |
|
|
Linked-need fields exposed to the template. |
|
|
Need-to-issue mapping; resolved against the config file. |
|
|
Whether status readback is active. |
|
|
Jira status name to tracked value. |
|
unset |
Text field carrying the need reference; omitted from the payload when unset. |
|
|
What |
|
|
Prefix of the need-id label on created issues. |
|
|
When to rebuild the mapping from Jira: |
|
unset |
Replaces the default project scan used by reconciliation. |
|
|
Also settable via |
|
|
|
|
config directory |
Where |