Skip to content

Jira Server / Data Center support (REST v2, PAT/Basic auth, wiki markup)#510

Merged
therealbrad merged 11 commits into
TestPlanIt:mainfrom
shokurov:feat/jira-server-datacenter
Jul 10, 2026
Merged

Jira Server / Data Center support (REST v2, PAT/Basic auth, wiki markup)#510
therealbrad merged 11 commits into
TestPlanIt:mainfrom
shokurov:feat/jira-server-datacenter

Conversation

@shokurov

@shokurov shokurov commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the Jira integration work against self-hosted Jira Server / Data Center
instances (closes #494).

Previously the integration was hardcoded for Atlassian Cloud: every call used
/rest/api/3 (Data Center only ships v2), API-key auth was always
Basic email:apiToken (a DC PAT must be sent as Bearer), users were addressed
by accountId (DC uses name/key), and descriptions were written as ADF
(DC expects Jira Wiki Markup). Any self-hosted instance failed with 404/401 on
the very first probe.

What changed

  • New dialect module jiraDeployment.ts — centralizes every Cloud vs
    Server/DC difference instead of scattering ternaries through the adapter:
    deployment detection (serverInfo probe + hostname heuristic fallback),
    auth-scheme resolution (Cloud Basic email:apiToken; DC Bearer PAT — even
    when an email is supplied — or Basic username:password), user refs
    (accountId vs name/key), and user-picker custom-field remapping.
  • JiraAdapter: dialect-aware endpoints — v3 vs v2 paths, DC's bare-array
    GET /project vs Cloud's { values } from /project/search, classic
    /search vs enhanced /search/jql, createmeta differences for DC 9+.
    Deployment detection uses a v3 /myself probe with redirect: "manual" so a
    DC instance that 302-redirects unknown v3 paths to its login page isn't
    misread as a healthy Cloud endpoint; the resolved deployment/auth scheme is
    persisted in integration settings, so detection is a one-time event.
  • Test-connection route mirrors the same detection, accepts all three
    credential shapes, probes the correct per-deployment endpoints, and persists
    the resolved deploymentType/authScheme (fill-missing-only, so a re-test
    never flips a working integration).
  • Rich text on DC — new jiraWikiMarkup.ts serializes the editor's
    ADF to Jira Wiki Markup (marks, headings, lists, quotes, code blocks, tables)
    instead of stripping descriptions/comments to plain text; the ADF path is
    unchanged for Cloud.
  • UI: Username/Password fields for DC Basic auth (previously the adapter
    accepted that shape but the form couldn't produce it), plus docs for both DC
    credential flows.
  • i18n — the six new and four changed form strings translated across all
    15 non-source locales.

Testing

  • ~1,600 lines of new unit tests: JiraAdapter dialect behavior (876), the
    test-connection route (330), deployment detection/auth resolution (213), and
    the wiki-markup serializer (179).
  • Verified live against a real Jira Data Center 10.3.13 instance (PAT and
    username/password flows): connection test, JQL search, issue read, and
    rich-text round-trip (wiki markup renders bold/headings/lists/links/code via
    Jira's own renderer).
  • Cloud behavior unchanged: v3 + Basic email:apiToken paths are the same code
    routed through the dialect module; existing Cloud tests pass.

Notes for reviewers

  • The dialect module is deliberately dependency-free and side-effect-free so
    it can be unit-tested exhaustively; JiraAdapter and the test-connection
    route share it rather than duplicating detection.
  • A companion PR splits out an unrelated zenstack generate memory fix.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

How Has This Been Tested?

Describe the tests you ran to verify your changes:

  • Unit tests
  • Integration tests
  • E2E tests
  • Manual testing

Test Configuration:

  • OS: Linux testplanit 7.0.0-22-generic Feature/api token support #22-Ubuntu SMP PREEMPT_DYNAMIC Mon May 25 15:54:34 UTC 2026 x86_64 GNU/Linux
  • Browser (if applicable): MS Edge on Windows, Arc on MacOS
  • Node version: v24.15.0

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published
  • I have signed the CLA

Screenshots (if applicable)

image

rpsft and others added 9 commits July 10, 2026 15:06
…ata Center

Jira Server / Data Center differs from Cloud in payload formats, endpoint
existence, query parameters, pagination, and response shapes — not just
the API version number. Centralize every one of those differences in a
single module instead of scattering deployment ternaries through the
adapter:

- resolveAuthScheme / buildAuthHeader — Cloud pairs an email with an API
  token (Basic); Data Center sends a Personal Access Token as Bearer even
  when an email happens to be supplied, and a username+password pair as
  Basic. An explicit override (from persisted settings) always wins.
- detectJiraDeployment — probes /rest/api/2/serverInfo (present on both
  deployments) and falls back to a hostname heuristic
  (*.atlassian.net/*.jiracloud.com => Cloud) when the probe is blocked.
- pickUserId / userRefField — Cloud addresses users by accountId; Server/
  Data Center by name (falling back to key).
- mapCustomFieldUserRefs — the create-issue form always emits a
  user-picker value as { accountId } regardless of deployment; on Data
  Center this must become { name } or Jira rejects the write.
- contentToString — Data Center's REST API v2 expects descriptions and
  comment bodies as plain strings, not Atlassian Document Format; extracts
  plain text from TipTap/ADF JSON, strips HTML, or passes a plain string
  through unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ta Center

The Jira integration was hardcoded for Atlassian Cloud: every call used
/rest/api/3 (Data Center only ships v2), API-key auth was always
Basic email:apiToken (a Data Center PAT must be sent as Bearer), and
users were addressed by accountId (Data Center uses name/key). Result:
HTTP 404/401 against any self-hosted Jira instance.

Using the new jiraDeployment.ts dialect module throughout JiraAdapter:

- Auth: detect the deployment via a v3 /myself probe with
  redirect:"manual" (so a Data Center instance that redirects unknown v3
  paths to its login page isn't misread as a successful Cloud probe);
  any non-OK result falls back to serverInfo detection, then re-resolves
  the auth scheme and retries on v2. An explicit deployment override
  short-circuits detection entirely.
- Project list: Data Center's GET /project returns a bare array; Cloud's
  GET /project/search returns { values: [...] }.
- Create meta: Data Center 9+ removed the classic
  createmeta?expand=... endpoint in favor of
  /issue/createmeta/{projectKey}/issuetypes/{issueTypeId}, which returns
  a differently-shaped paginated { values } response.
- Create/update issue: descriptions and comment bodies are Atlassian
  Document Format on Cloud (converting TipTap JSON, HTML, or plain
  strings to ADF) and plain strings on Data Center. Reporter, assignee,
  and user-picker custom fields are routed through one userRef mapper
  instead of being sent as Cloud's { accountId } shape unconditionally.
- Search: Data Center's classic /search paginates via startAt/total;
  Cloud's enhanced /search/jql pages via an opaque nextPageToken and
  reports isLast instead of a total count. Data Center has no cursor of
  its own, so one is synthesized from startAt so callers that only
  advance via a page token (bulk project import) don't re-read page 1
  forever.
- User search: Data Center's /user/search takes ?username=, not ?query=.
- makeRequest: Jira returns 204 No Content for several write endpoints
  (issue update, transition execution) on both deployments; unconditionally
  calling response.json() throws "Unexpected end of JSON input" on the
  empty body. This was a real, previously-unexercised crash — a
  transition would succeed in Jira and then the adapter would throw
  immediately after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The test-connection route carried its own hardcoded Cloud-only probe
(fixed /rest/api/3, Basic email:apiToken) — a Data Center instance
would always fail here even once the adapter itself supported it,
since this route builds its own request rather than going through
JiraAdapter.

- Mirrors the adapter's detection: a v3 /myself probe with
  redirect:"manual" (so a Data Center login-page redirect isn't
  misread as a successful Cloud probe), falling back to serverInfo
  detection and a v2 retry with the auth scheme re-resolved for the
  discovered deployment. An explicit settings.deploymentType/authScheme
  override short-circuits detection.
- Accepts all three API-key credential shapes (Cloud email+apiToken,
  Data Center PAT, Data Center username+password), not just the first.
- The search-scope and read-issue capability probes use the correct
  endpoint per deployment (classic /search vs enhanced /search/jql;
  correct API version for /issue/picker).
- A bare API token with no email that a Cloud serverInfo detection
  confirms is Cloud now returns an explicit "Jira Cloud requires an
  email address paired with the API token" error instead of an opaque
  401 — the initial scheme guess (Bearer, since no email was paired)
  can never succeed against Cloud's Basic-only API-key auth.
- On success, the route now persists the detected deploymentType and
  resolved authScheme into the integration's settings, merged
  fill-missing-only (an already-resolved key always wins, so a later
  re-test can't flip a working integration's settings). Previously
  these were only optional and typically never written, so every
  adapter-cache miss re-ran the full detection probe chain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Data Center's documented Basic auth flow (username + password) had no
way to reach production: IntegrationConfigForm only had email/apiToken
fields, and IntegrationManager.getAdapter forwarded just
email/apiToken/personalAccessToken into the adapter's authData. The
adapter and test-connection route already accepted a
{ username, password } shape — it just wasn't reachable from the UI.

- IntegrationConfigForm: two new optional fields (Username, Password)
  alongside Email/API Token, all now optional since Jira's three
  credential shapes (Cloud email+token, Data Center PAT, Data Center
  Basic) each use a different subset.
- IntegrationManager.getAdapter forwards credentials.username/password
  into authData when present (a no-op for every other provider, which
  never populates those keys).
- Docs: new "Jira Server / Data Center" section covering both supported
  credential shapes, a Cloud-only callout on the OAuth section, and a
  curl example for the Bearer PAT flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…US.json

Caught during self-review: the previous commit's en-US.json came from a
wholesale `git checkout fix/jira-datacenter-494 -- ...` off a branch that
predates upstream's magic-link/passwordless sign-in feature, so it
silently reverted every "passwordless" key that feature had added.
Rebuilt from upstream/main's current file with only the Jira-specific
keys layered on top; verified the diff against upstream/main now touches
nothing outside the jira integration config strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Data Center's description field is Jira Wiki Markup, not unformatted
plain text (verified live: a wiki-markup string round-trips to fully
rendered HTML — bold, headings, lists, links, code — via Jira's own
renderer). The integration was stripping every create/update
description down to bare text through contentToString(), silently
discarding all formatting a user entered in the rich-text editor.

Write side:
- Replace contentToString() with adfToWikiMarkup() in jiraDeployment.ts,
  a serializer that mirrors the adapter's existing ADF->HTML reader:
  bold/italic/underline/strike/code/link marks, headings, nested and
  mixed bullet/ordered lists, block quotes, code blocks (with language),
  horizontal rules, hard breaks, and tables.
- JiraAdapter.toServerDescription() routes the editor's TipTap doc (and
  HTML) through the same tiptapToAdf/htmlToAdf converters already used
  for Cloud, then serializes to wiki markup; a bare string is already
  valid wiki markup and passes through unchanged. contentToString() and
  its extractTextFromNodes() helper are removed (now dead).

Read side (so formatting survives the round trip instead of showing raw
"*markup*" in TestPlanIt):
- getIssue requests expand=renderedFields on Server/DC and prefers
  Jira's own rendered HTML for the description; getIssueComments does the
  same via expand=renderedBody. Cloud is untouched — it keeps returning
  ADF parsed by adfToHtml.

Tests: 22 new adfToWikiMarkup unit tests (marks, headings, nested/mixed
lists, code blocks, quotes, rules, hard breaks, tables, block
separation) plus adapter-level DC tests asserting createIssue/updateIssue
emit wiki-markup strings and getIssue/getIssueComments consume rendered
HTML. Verified end-to-end against a live Jira DC 10.3.13 instance
driving the real adapter (a formatted TipTap description came back as
correct rendered HTML). eslint + tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…conversion for descriptions and comments

Addresses two review points on the rich-text change:

1. The wiki-markup serializer was a large, self-contained concern living
   in jiraDeployment.ts (which is about Cloud-vs-DC dialect decisions).
   Moved adfToWikiMarkup + its helpers to a dedicated jiraWikiMarkup.ts
   (with jiraWikiMarkup.test.ts), leaving jiraDeployment.ts focused on
   detection/auth/user-ref concerns.

2. Rich text is not description-only — issue comment bodies use the same
   wiki-markup (DC) / ADF (Cloud) grammar. The conversion was duplicated:
   createIssue and updateIssue each carried an identical ~35-line
   description block, and addComment had its own separate inline body
   construction. Unified all three onto one deployment-aware
   toJiraContent(input) helper (Server/DC -> wiki markup string, Cloud ->
   ADF doc, handling TipTap/HTML/plain input and empty values). Comments
   now go through the same pipeline, so they're no longer the one
   rich-text field silently limited to plain text.

Behavior is unchanged for every current caller: descriptions convert
exactly as before (unit assertions pin the wiki-markup output), and
addComment's sole caller (linkToTestCase) passes plain text, which
converts to itself on DC and a single ADF paragraph on Cloud — identical
to the previous hand-built body.

Verified: 131 unit tests green (dialect + wiki-markup + adapter); live
end-to-end against Jira DC 10.3.13 — a formatted TipTap description and
a comment both round-trip correctly through toJiraContent. eslint + tsc
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rich text on Server/DC is emitted as Jira Wiki Markup, which assumes the
Description/Comment fields use the default Wiki Style Renderer. A field
switched to the Default Text Renderer would show literal markup; Jira
exposes no renderer in createmeta/editmeta so it can't be auto-detected,
and the plain-text-renderer config is uncommon with a cosmetic,
admin-fixable failure mode — so we assume the wiki renderer and document
it rather than adding a setting.

- jiraWikiMarkup.ts: note the renderer assumption, and clarify it's
  distinct from the per-user "Rich Text Editing" (WYSIWYG) preference,
  which does not affect the stored format.
- integrations.md: user-facing note in the Server/DC section on rich-text
  support and how to fix a field stuck on the Default Text Renderer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Jira Server / Data Center work added six new form strings
(username/password labels, placeholders, help texts) and rewrote four
existing ones (email/API-token help, Jira URL placeholder/help) — but
only in en-US.json. Every other locale rendered raw i18n keys for the
new fields (admin.integrations.config.username, ...Placeholder, etc.)
and stale pre-DC guidance for the changed ones. Caught during UAT on a
ru-RU session.

Adds translations for all six new keys and updates the four changed
ones across the 15 non-source locales, matching each file's existing
terminology (ru «API-токен» / «Введите свой…», zh-CN 帐户/令牌,
zh-TW 帳戶/權杖 with 產生/設定, tr «belirteç», vi «mã thông báo»,
de Sie-form with „…“ quotes, pl «Twojego konta», etc.). Technical
tokens (Jira Server / Data Center, Jira Cloud, Basic, Bearer, PAT)
stay untranslated, as elsewhere in these files. ar-SA's Jira config
section is untranslated English in the source file, so its new strings
follow that existing state rather than introducing lone Arabic entries.

Keys are inserted at the same position as in en-US (after
apiTokenHelp), so every locale's config block stays structurally
identical to the source — verified: key set AND order now match en-US
in all 15 files, all files parse, and the diff is exactly the intended
10 lines per file (whole-file JSON round-trip proven byte-identical
before rewriting).

Note for upstreaming: upstream localizes via Crowdin (crowdin-sync
workflow), which may retranslate these — these entries make the fork
usable in non-English locales now and serve as seeds/suggestions for
Crowdin later.

IntegrationConfigForm + i18n tests: 29/29 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@therealbrad

therealbrad commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@shokurov Thanks for this — the dialect module and the live DC 10.3.13 validation (PAT + Basic, rich-text round-trip) make this a much stronger footing than testing against mocks alone. A few small items before merge, all minor:

1. searchUsers: the assignable-user branch still sends the Cloud-only query= param on Data Center

In JiraAdapter.searchUsers, the general-search branch correctly switches to username= on server, but the adjacent assignable-search branch keeps query=:

if (projectKey && !isEmail) {
  // still query= on server
  endpoint = `/rest/api/${this.apiVersion}/user/assignable/search?project=${projectKey}&query=${encodeURIComponent(query)}&startAt=${startAt}&maxResults=${maxResults}`;
} else {
  // fixed
  endpoint = `/rest/api/${this.apiVersion}/user/search?${this.deployment === "server" ? "username" : "query"}=${encodeURIComponent(query)}&...`;
}

On Server/DC, /user/assignable/search filters by username (query is a Cloud-only param), so the assignee picker scoped to a project — when the term isn't an email — should return unfiltered results on DC. Since you have a live instance and I don't, could you confirm the behavior there and, if it reproduces, apply the same ternary?

const userParam = this.deployment === "server" ? "username" : "query";
endpoint = `/rest/api/${this.apiVersion}/user/assignable/search?project=${projectKey}&${userParam}=${encodeURIComponent(query)}&startAt=${startAt}&maxResults=${maxResults}`;

(If your DC version happens to accept query here too, a one-line comment noting that is enough — no code change needed.)

2. Dead branch in the v3 probe flow (cosmetic)

In both JiraAdapter.authenticate and testJiraConnection (route.ts), the probe reads:

if (v3Probe.ok) {  }
else if (!v3Probe.ok) {  }   // always true when reached
else { connection = v3Probe }  // unreachable

Since v3Probe.ok is boolean, the final else can't be hit — collapsing to a plain if/else reads clearer.

3. redirect: "manual" comment/test wording (cosmetic)

The "opaque redirect (non-OK, status 0)" comment and the status: 0 test mock describe browser fetch semantics; server-side (undici) redirect: "manual" returns the real 3xx response. The logic is correct either way (any non-ok falls through to serverInfo), so just a wording tweak on the comment/test if you touch it.

Everything else looks good to merge. One non-blocking question: I noticed the createmeta path is 9+/issuetypes/{id} only — was dropping the Jira 8.x classic-createmeta fallback intentional? Fine either way given 8.x is EOL, just want it to be a deliberate choice rather than an oversight.

@therealbrad

Copy link
Copy Markdown
Contributor

https://github.com/TestPlanIt/testplanit/actions/runs/29085381733/job/86366369049 failed. Please run prettier on the branch so this passes next time.

rpsft and others added 2 commits July 10, 2026 20:46
Review follow-ups for PR TestPlanIt#510:

- /user/assignable/search: send username= instead of the Cloud-only
  query= param on Server/DC. Verified live on DC 10.3.13: query= is
  silently ignored (returns the full unfiltered assignable list, even
  for a term matching nothing), username= filters correctly. Covered
  by a new unit test.
- Collapse the unreachable else branches in the v3 probe flows of
  JiraAdapter.authenticate and testJiraConnection (v3Probe.ok is
  boolean, so if/else-if(!ok)/else had a dead tail).
- Reword the redirect: "manual" comments and test mock to describe
  server-side (undici) semantics: the probe sees the real 3xx response
  (non-OK), not a browser-style opaque status-0 response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Formatting-only; fixes the failed format check in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shokurov

Copy link
Copy Markdown
Contributor Author

@therealbrad Thanks for the careful read — all items addressed in dd91f31 + 55ead7b.

1. Confirmed on live DC 10.3.13: query= on /user/assignable/search is silently ignored — a term matching nothing still returns the full assignable list (14 users), while username= filters correctly (2 matches for a real prefix, 0 for a nonsense term). Applied the same ternary as the general-search branch, with a comment noting the live verification, plus a unit test covering the assignable branch.

2. Dead else branches collapsed in both JiraAdapter.authenticate and testJiraConnection.

3. Reworded the redirect: "manual" comments and the test mock to describe server-side (undici) semantics — the probe sees the real 3xx response (non-OK), not a browser-style opaque status-0 response. The mock now returns status: 302.

Re createmeta: yes, dropping the 8.x classic-createmeta fallback was deliberate — Jira 8.x is EOL, and the 9+ createmeta/{key}/issuetypes/{id} path is what we validated against DC 10.3.13. Happy to add the fallback if 8.x support ever becomes a requirement.

CI: prettier run on the branch (the style: commit), format check passes locally now.

@therealbrad
therealbrad merged commit c18d812 into TestPlanIt:main Jul 10, 2026
1 check passed
@therealbrad

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.41.6 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

therealbrad pushed a commit that referenced this pull request Jul 10, 2026
…, wiki markup) (#510)

* feat(jira): add a per-deployment dialect module for Cloud vs Server/Data Center

Jira Server / Data Center differs from Cloud in payload formats, endpoint
existence, query parameters, pagination, and response shapes — not just
the API version number. Centralize every one of those differences in a
single module instead of scattering deployment ternaries through the
adapter:

- resolveAuthScheme / buildAuthHeader — Cloud pairs an email with an API
  token (Basic); Data Center sends a Personal Access Token as Bearer even
  when an email happens to be supplied, and a username+password pair as
  Basic. An explicit override (from persisted settings) always wins.
- detectJiraDeployment — probes /rest/api/2/serverInfo (present on both
  deployments) and falls back to a hostname heuristic
  (*.atlassian.net/*.jiracloud.com => Cloud) when the probe is blocked.
- pickUserId / userRefField — Cloud addresses users by accountId; Server/
  Data Center by name (falling back to key).
- mapCustomFieldUserRefs — the create-issue form always emits a
  user-picker value as { accountId } regardless of deployment; on Data
  Center this must become { name } or Jira rejects the write.
- contentToString — Data Center's REST API v2 expects descriptions and
  comment bodies as plain strings, not Atlassian Document Format; extracts
  plain text from TipTap/ADF JSON, strips HTML, or passes a plain string
  through unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(jira): dialect-aware endpoints, auth, and user refs for Server/Data Center

The Jira integration was hardcoded for Atlassian Cloud: every call used
/rest/api/3 (Data Center only ships v2), API-key auth was always
Basic email:apiToken (a Data Center PAT must be sent as Bearer), and
users were addressed by accountId (Data Center uses name/key). Result:
HTTP 404/401 against any self-hosted Jira instance.

Using the new jiraDeployment.ts dialect module throughout JiraAdapter:

- Auth: detect the deployment via a v3 /myself probe with
  redirect:"manual" (so a Data Center instance that redirects unknown v3
  paths to its login page isn't misread as a successful Cloud probe);
  any non-OK result falls back to serverInfo detection, then re-resolves
  the auth scheme and retries on v2. An explicit deployment override
  short-circuits detection entirely.
- Project list: Data Center's GET /project returns a bare array; Cloud's
  GET /project/search returns { values: [...] }.
- Create meta: Data Center 9+ removed the classic
  createmeta?expand=... endpoint in favor of
  /issue/createmeta/{projectKey}/issuetypes/{issueTypeId}, which returns
  a differently-shaped paginated { values } response.
- Create/update issue: descriptions and comment bodies are Atlassian
  Document Format on Cloud (converting TipTap JSON, HTML, or plain
  strings to ADF) and plain strings on Data Center. Reporter, assignee,
  and user-picker custom fields are routed through one userRef mapper
  instead of being sent as Cloud's { accountId } shape unconditionally.
- Search: Data Center's classic /search paginates via startAt/total;
  Cloud's enhanced /search/jql pages via an opaque nextPageToken and
  reports isLast instead of a total count. Data Center has no cursor of
  its own, so one is synthesized from startAt so callers that only
  advance via a page token (bulk project import) don't re-read page 1
  forever.
- User search: Data Center's /user/search takes ?username=, not ?query=.
- makeRequest: Jira returns 204 No Content for several write endpoints
  (issue update, transition execution) on both deployments; unconditionally
  calling response.json() throws "Unexpected end of JSON input" on the
  empty body. This was a real, previously-unexercised crash — a
  transition would succeed in Jira and then the adapter would throw
  immediately after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(jira): detect and persist deployment/auth scheme in test-connection

The test-connection route carried its own hardcoded Cloud-only probe
(fixed /rest/api/3, Basic email:apiToken) — a Data Center instance
would always fail here even once the adapter itself supported it,
since this route builds its own request rather than going through
JiraAdapter.

- Mirrors the adapter's detection: a v3 /myself probe with
  redirect:"manual" (so a Data Center login-page redirect isn't
  misread as a successful Cloud probe), falling back to serverInfo
  detection and a v2 retry with the auth scheme re-resolved for the
  discovered deployment. An explicit settings.deploymentType/authScheme
  override short-circuits detection.
- Accepts all three API-key credential shapes (Cloud email+apiToken,
  Data Center PAT, Data Center username+password), not just the first.
- The search-scope and read-issue capability probes use the correct
  endpoint per deployment (classic /search vs enhanced /search/jql;
  correct API version for /issue/picker).
- A bare API token with no email that a Cloud serverInfo detection
  confirms is Cloud now returns an explicit "Jira Cloud requires an
  email address paired with the API token" error instead of an opaque
  401 — the initial scheme guess (Bearer, since no email was paired)
  can never succeed against Cloud's Basic-only API-key auth.
- On success, the route now persists the detected deploymentType and
  resolved authScheme into the integration's settings, merged
  fill-missing-only (an already-resolved key always wins, so a later
  re-test can't flip a working integration's settings). Previously
  these were only optional and typically never written, so every
  adapter-cache miss re-ran the full detection probe chain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(jira): add Username/Password fields for Data Center Basic auth

Data Center's documented Basic auth flow (username + password) had no
way to reach production: IntegrationConfigForm only had email/apiToken
fields, and IntegrationManager.getAdapter forwarded just
email/apiToken/personalAccessToken into the adapter's authData. The
adapter and test-connection route already accepted a
{ username, password } shape — it just wasn't reachable from the UI.

- IntegrationConfigForm: two new optional fields (Username, Password)
  alongside Email/API Token, all now optional since Jira's three
  credential shapes (Cloud email+token, Data Center PAT, Data Center
  Basic) each use a different subset.
- IntegrationManager.getAdapter forwards credentials.username/password
  into authData when present (a no-op for every other provider, which
  never populates those keys).
- Docs: new "Jira Server / Data Center" section covering both supported
  credential shapes, a Cloud-only callout on the OAuth section, and a
  curl example for the Bearer PAT flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(jira): stop reverting upstream's passwordless i18n strings in en-US.json

Caught during self-review: the previous commit's en-US.json came from a
wholesale `git checkout fix/jira-datacenter-494 -- ...` off a branch that
predates upstream's magic-link/passwordless sign-in feature, so it
silently reverted every "passwordless" key that feature had added.
Rebuilt from upstream/main's current file with only the Jira-specific
keys layered on top; verified the diff against upstream/main now touches
nothing outside the jira integration config strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(jira): preserve rich text on Server/Data Center via Jira Wiki Markup

Data Center's description field is Jira Wiki Markup, not unformatted
plain text (verified live: a wiki-markup string round-trips to fully
rendered HTML — bold, headings, lists, links, code — via Jira's own
renderer). The integration was stripping every create/update
description down to bare text through contentToString(), silently
discarding all formatting a user entered in the rich-text editor.

Write side:
- Replace contentToString() with adfToWikiMarkup() in jiraDeployment.ts,
  a serializer that mirrors the adapter's existing ADF->HTML reader:
  bold/italic/underline/strike/code/link marks, headings, nested and
  mixed bullet/ordered lists, block quotes, code blocks (with language),
  horizontal rules, hard breaks, and tables.
- JiraAdapter.toServerDescription() routes the editor's TipTap doc (and
  HTML) through the same tiptapToAdf/htmlToAdf converters already used
  for Cloud, then serializes to wiki markup; a bare string is already
  valid wiki markup and passes through unchanged. contentToString() and
  its extractTextFromNodes() helper are removed (now dead).

Read side (so formatting survives the round trip instead of showing raw
"*markup*" in TestPlanIt):
- getIssue requests expand=renderedFields on Server/DC and prefers
  Jira's own rendered HTML for the description; getIssueComments does the
  same via expand=renderedBody. Cloud is untouched — it keeps returning
  ADF parsed by adfToHtml.

Tests: 22 new adfToWikiMarkup unit tests (marks, headings, nested/mixed
lists, code blocks, quotes, rules, hard breaks, tables, block
separation) plus adapter-level DC tests asserting createIssue/updateIssue
emit wiki-markup strings and getIssue/getIssueComments consume rendered
HTML. Verified end-to-end against a live Jira DC 10.3.13 instance
driving the real adapter (a formatted TipTap description came back as
correct rendered HTML). eslint + tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(jira): extract wiki markup to its own module; unify content conversion for descriptions and comments

Addresses two review points on the rich-text change:

1. The wiki-markup serializer was a large, self-contained concern living
   in jiraDeployment.ts (which is about Cloud-vs-DC dialect decisions).
   Moved adfToWikiMarkup + its helpers to a dedicated jiraWikiMarkup.ts
   (with jiraWikiMarkup.test.ts), leaving jiraDeployment.ts focused on
   detection/auth/user-ref concerns.

2. Rich text is not description-only — issue comment bodies use the same
   wiki-markup (DC) / ADF (Cloud) grammar. The conversion was duplicated:
   createIssue and updateIssue each carried an identical ~35-line
   description block, and addComment had its own separate inline body
   construction. Unified all three onto one deployment-aware
   toJiraContent(input) helper (Server/DC -> wiki markup string, Cloud ->
   ADF doc, handling TipTap/HTML/plain input and empty values). Comments
   now go through the same pipeline, so they're no longer the one
   rich-text field silently limited to plain text.

Behavior is unchanged for every current caller: descriptions convert
exactly as before (unit assertions pin the wiki-markup output), and
addComment's sole caller (linkToTestCase) passes plain text, which
converts to itself on DC and a single ADF paragraph on Cloud — identical
to the previous hand-built body.

Verified: 131 unit tests green (dialect + wiki-markup + adapter); live
end-to-end against Jira DC 10.3.13 — a formatted TipTap description and
a comment both round-trip correctly through toJiraContent. eslint + tsc
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(jira): document the Wiki Style Renderer assumption for DC rich text

Rich text on Server/DC is emitted as Jira Wiki Markup, which assumes the
Description/Comment fields use the default Wiki Style Renderer. A field
switched to the Default Text Renderer would show literal markup; Jira
exposes no renderer in createmeta/editmeta so it can't be auto-detected,
and the plain-text-renderer config is uncommon with a cosmetic,
admin-fixable failure mode — so we assume the wiki renderer and document
it rather than adding a setting.

- jiraWikiMarkup.ts: note the renderer assumption, and clarify it's
  distinct from the per-user "Rich Text Editing" (WYSIWYG) preference,
  which does not affect the stored format.
- integrations.md: user-facing note in the Server/DC section on rich-text
  support and how to fix a field stuck on the Default Text Renderer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* i18n(jira): localize the Data Center auth strings in all 15 locales

The Jira Server / Data Center work added six new form strings
(username/password labels, placeholders, help texts) and rewrote four
existing ones (email/API-token help, Jira URL placeholder/help) — but
only in en-US.json. Every other locale rendered raw i18n keys for the
new fields (admin.integrations.config.username, ...Placeholder, etc.)
and stale pre-DC guidance for the changed ones. Caught during UAT on a
ru-RU session.

Adds translations for all six new keys and updates the four changed
ones across the 15 non-source locales, matching each file's existing
terminology (ru «API-токен» / «Введите свой…», zh-CN 帐户/令牌,
zh-TW 帳戶/權杖 with 產生/設定, tr «belirteç», vi «mã thông báo»,
de Sie-form with „…“ quotes, pl «Twojego konta», etc.). Technical
tokens (Jira Server / Data Center, Jira Cloud, Basic, Bearer, PAT)
stay untranslated, as elsewhere in these files. ar-SA's Jira config
section is untranslated English in the source file, so its new strings
follow that existing state rather than introducing lone Arabic entries.

Keys are inserted at the same position as in en-US (after
apiTokenHelp), so every locale's config block stays structurally
identical to the source — verified: key set AND order now match en-US
in all 15 files, all files parse, and the diff is exactly the intended
10 lines per file (whole-file JSON round-trip proven byte-identical
before rewriting).

Note for upstreaming: upstream localizes via Crowdin (crowdin-sync
workflow), which may retranslate these — these entries make the fork
usable in non-English locales now and serve as seeds/suggestions for
Crowdin later.

IntegrationConfigForm + i18n tests: 29/29 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(jira): filter assignable user search by username= on Server/DC

Review follow-ups for PR #510:

- /user/assignable/search: send username= instead of the Cloud-only
  query= param on Server/DC. Verified live on DC 10.3.13: query= is
  silently ignored (returns the full unfiltered assignable list, even
  for a term matching nothing), username= filters correctly. Covered
  by a new unit test.
- Collapse the unreachable else branches in the v3 probe flows of
  JiraAdapter.authenticate and testJiraConnection (v3Probe.ok is
  boolean, so if/else-if(!ok)/else had a dead tail).
- Reword the redirect: "manual" comments and test mock to describe
  server-side (undici) semantics: the probe sees the real 3xx response
  (non-OK), not a browser-style opaque status-0 response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: run prettier on the jira-server-datacenter branch

Formatting-only; fixes the failed format check in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Egor Shokurov <egors@rapidsoft.ru>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
shokurov pushed a commit to shokurov/testplanit that referenced this pull request Jul 14, 2026
## [0.41.6](TestPlanIt/testplanit@v0.41.5...v0.41.6) (2026-07-10)

### Bug Fixes

* **jira:** Jira Server / Data Center support (REST v2, PAT/Basic auth, wiki markup) ([TestPlanIt#510](TestPlanIt#510)) ([c18d812](TestPlanIt@c18d812))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Jira integration does not work with Server/Data Center — hardcoded to Cloud-only /rest/api/3 (contradicts docs)

3 participants