Skip to content

feat(jira): Cloud support PR-1 — deployment-type + OAuth2 config data model#416

Merged
CybotTM merged 4 commits into
mainfrom
feat/jira-cloud-data-model
Jun 22, 2026
Merged

feat(jira): Cloud support PR-1 — deployment-type + OAuth2 config data model#416
CybotTM merged 4 commits into
mainfrom
feat/jira-cloud-data-model

Conversation

@CybotTM

@CybotTM CybotTM commented Jun 22, 2026

Copy link
Copy Markdown
Member

What

PR-1 of the Jira Cloud support feature (design: claudedocs/jira-cloud-design.md). This is the data-model + admin-plumbing foundation with no behaviour change — the factory still builds the existing OAuth-1 service for every ticket system; these are just new persisted + admin-editable fields that later PRs will branch on.

Confirmed decisions baked in: per-ticket-system OAuth-2 credentials (B2), a deployment_type discriminator on TicketSystem (A), and explicit new token columns (C).

Changes

  • DeploymentType enum (SERVER | CLOUD, default SERVER).
  • TicketSystem: deployment_type, oauth2_client_id, oauth2_client_secret, cloud_id. Only the oauth2 client secret joins SECRET_KEYS (stripped from the list/toSafeArray); oauth2_client_id / deployment_type / cloud_id are non-secret.
  • UserTicketsystem: refresh_token (encrypted in a later PR) + token_expires_at for the OAuth-2 refresh lifecycle; tokensecret relaxed to nullable for Cloud rows.
  • TicketSystemSaveDto maps the new admin-entered fields (not cloud_id, which is server-resolved); SaveTicketSystemAction preserves a blank oauth2 client secret on save.
  • Doctrine migration (Version20260622_AddJiraCloudSupport, symmetric up/down) + sql/full.sql kept in sync.
  • Admin form (frontend/src/admin/entities.ts): deployment-type select + OAuth-2 client id/secret, seeded in both new-row and edit-row states; no cloud_id field. EN/DE labels.

Verification

  • Full PHP suite green: 2010 tests, 6014 assertions (the non-zero exit is the pre-existing OptimizedEntryRepositoryCacheTest two-suites runner warning). PHPStan no errors, PHP-CS-Fixer clean.
  • Frontend: typecheck / lint / bun test (266) / build all green.
  • New tests cover the enum, the entity getters/setters, toSafeArray stripping the oauth2 secret (both spellings) while keeping the non-secrets, the DTO defaults/mapping, and the preserve-on-blank behaviour.

Note for reviewers

This is a schema change: the persistent unittest DB volume needs a rebuild (make reset-test-db) before make test — CI builds the DB fresh from sql/full.sql, so it's unaffected.

Follow-up PRs (per the design)

PR-2 OAuth-2 service + callback · PR-3 Cloud read client (/search/jql) + factory branch · PR-4 Cloud worklog write · PR-5 admin field-visibility polish. (PR-2's real end-to-end test needs the Atlassian app registration — pending.)

CybotTM added 2 commits June 22, 2026 19:21
…ystem model

Data-model foundation for Jira Cloud support (no behaviour change — the factory
still builds the OAuth-1 service for every ticket system). Adds a per-ticket-
system deployment discriminator and OAuth 2.0 credential storage so a later PR
can branch the transport/auth by deployment type:

- DeploymentType enum (SERVER | CLOUD), default SERVER.
- TicketSystem: deployment_type, oauth2_client_id, oauth2_client_secret, cloud_id.
  Only the oauth2 client secret joins SECRET_KEYS (stripped from the list/safe
  view); deployment_type, oauth2_client_id and cloud_id are non-secret.
- UserTicketsystem: refresh_token (encrypted later) + token_expires_at, for the
  OAuth 2.0 refresh lifecycle; tokensecret relaxed to nullable for Cloud rows.
- TicketSystemSaveDto maps the new admin-entered fields (not cloud_id, which is
  server-resolved); SaveTicketSystemAction preserves a blank oauth2 client secret.
- Doctrine migration + sql/full.sql kept in sync.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Surface the new ticket-system fields in the SolidJS admin: a deployment-type
select (Server/DC vs Cloud) plus the OAuth 2.0 client id/secret, seeded blank in
both new-row and edit-row form states. cloud_id is intentionally not editable
(server-resolved at first auth). EN/DE labels added.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Copilot AI review requested due to automatic review settings June 22, 2026 17:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for Jira Cloud (OAuth 2.0 / 3LO) alongside the existing Jira Server (OAuth 1.0a) implementation. It adds the necessary database columns, updates the backend entities, DTOs, and controllers to manage deployment types and OAuth 2.0 credentials, and updates the frontend administration forms and translation files. While the changes are well-tested, three issues were identified in the review: first, the frontend form currently hardcodes oauth2ClientId to an empty string when editing, which will overwrite the stored database value on save; second, the token_expires_at field should use immutable datetime types to prevent side effects; and third, the migration rollback (down method) should restore tokensecret to its original VARCHAR(50) type rather than TEXT.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread frontend/src/admin/entities.ts Outdated
Comment thread src/Entity/UserTicketsystem.php Outdated
Comment thread migrations/Version20260622_AddJiraCloudSupport.php
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.89%. Comparing base (9e5fb0e) to head (9f74814).
⚠️ Report is 10 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main     #416      +/-   ##
============================================
+ Coverage     83.72%   83.89%   +0.17%     
- Complexity     2712     2746      +34     
============================================
  Files           184      186       +2     
  Lines          7442     7521      +79     
============================================
+ Hits           6231     6310      +79     
  Misses         1211     1211              
Flag Coverage Δ
integration 52.01% <53.65%> (+0.73%) ⬆️
unit 50.03% <92.68%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

CybotTM added 2 commits June 22, 2026 19:42
… expiry

- Admin edit-row now reads oauth2_client_id from the row instead of blanking it.
  It's a non-secret field returned by toSafeArray(), so the previous blank seed
  would have overwritten the stored client id with '' on save.
- token_expires_at uses datetime_immutable / ?DateTimeImmutable (no mutation
  side effects); no DDL change (same DATETIME column).
- The down() migration blanks any NULL tokensecret before restoring NOT NULL, so
  rolling back can't fail on a Cloud row that wrote a null secret. (It reverts to
  TEXT NOT NULL — the immediately-preceding state from Version20250901 — not the
  original VARCHAR(50).)
- Collapsed the repetitive nullable-string accessor tests into one explicit
  method (was tripping the new-code duplication gate).

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
… block

The "re-fetch ticket system 1 from a cleared manager" block was repeated in
each save test, which tripped the new-code duplication gate. Extract it into a
shared helper used by all three so the block exists once.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
@CybotTM CybotTM merged commit 72f1ade into main Jun 22, 2026
25 checks passed
@CybotTM CybotTM deleted the feat/jira-cloud-data-model branch June 22, 2026 18:00
@sonarqubecloud

Copy link
Copy Markdown

CybotTM added a commit that referenced this pull request Jul 2, 2026
…ide with screenshots, ADR record, agent rules (#494)

## Summary

Full documentation overhaul: a verified-facts rewrite of the guides, a
new illustrated user guide, a corrected ADR record, refreshed agent
rules, an extended in-app Help page, and a full-structure README.

A 5-domain audit against the actual code found that, beyond routine
drift, several large docs (DEPLOYMENT_GUIDE, CODE_EXAMPLES,
TROUBLESHOOTING, configuration, development, testing, and ADRs 003–013)
described **infrastructure this application never had** — Redis layers,
JWT auth, `/api/v1`, ParaTest/Panther suites, ~50 invented env vars and
`app:*` console commands. Everything replaced here was re-derived from
`Makefile`, `composer.json`, `compose.yml`, `.env*`, `config/`, `src/`
and the real CI workflows; every command, route, env var and version in
the new docs was verified to exist.

## What's in here

- **New [user guide](docs/user-guide.md)** covering every feature, with
20 English 1440px screenshots — including the QoL features (dark mode,
compact density, left/right sidebar navigation, Alt shortcut badges);
[features.md](docs/features.md) and [FAQ.md](docs/FAQ.md) rewritten for
the current SolidJS UI
- **README expanded to a full project page**: TOC, About, grouped
features, screenshot gallery, first-login credentials for the dev stack,
key-config table, mermaid architecture diagram,
Usage/Testing/Deployment/Contributing/Security sections
- **Developer guides rewritten** ([development](docs/development.md),
[configuration](docs/configuration.md), [testing](docs/testing.md)) —
~5,300 lines of fiction replaced by ~1,300 verified lines; configuration
now documents the real ticket-system admin fields (SERVER OAuth 1.0a vs
CLOUD OAuth 2.0) and internal-Jira ticket mirroring
- **Ops docs rewritten** ([deployment](docs/DEPLOYMENT_GUIDE.md),
[troubleshooting](docs/TROUBLESHOOTING.md)) from `compose.yml` /
`docker-bake.hcl` / the publish workflow
- **ADR record corrected**: index now lists all 17 ADRs; dated *reality
notes* on ADRs whose bodies describe never-built infrastructure (bodies
kept as history); new
[ADR-015](docs/adr/ADR-015-php-8-5-symfony-8-upgrade.md) (PHP
8.5/Symfony 8), [ADR-016](docs/adr/ADR-016-solidjs-frontend-rewrite.md)
(ExtJS→SolidJS, #470/#490),
[ADR-017](docs/adr/ADR-017-jira-cloud-oauth2.md) (dual-mode Jira auth,
#416)
- **API/internals references** (api.md, DTO/EVENT/REPOSITORY docs)
aligned with the code — all 65 endpoint headings now map to real routes
- **AGENTS.md**: drift fixed (PHPStan 10, PER-CS), new scoped files for
`frontend/` and `e2e/`, `CLAUDE.md` symlinks restored at all five levels
- **In-app Help** (`/ui/help`): new "The pages" section (EN+DE) and a
link to the user guide — lint/typecheck/346 Vitest tests green
- **CONTRIBUTING**: the enforced-but-undocumented DCO sign-off
requirement is now documented
- **Removed**: `README.rst` (stale duplicate), `TASKS.md` (abandoned
2025 notes), `docs/CODE_EXAMPLES.md` (68 KB of examples for services
that never existed); all references updated

## Review

Copilot raised 2 inline comments (PER-CS leftover in src/AGENTS.md,
404ing v4_EOL release link) — both fixed in 41fee70, threads replied to
and resolved. Gemini reviewed with no findings.

## Verification

- Every documented `make`/`composer`/`bin/console` command checked
against `Makefile`, `composer.json` scripts and `src/Command/`
- All relative links and image references across 58 markdown files
resolve
- ADR PR/commit references verified via GitHub (#416, #470, #490)
- Frontend change: `bun run lint`, `bun run typecheck`, `bun run test`
(346 passing), page visually verified in the running e2e stack
- Host unit suite green (1557 tests) via the CaptainHook pre-commit gate
on every commit

## Follow-ups (out of scope, found during verification)

- `docker/nginx/default.conf` forwards to `phpfpm:9000` but the prod
compose service is named `app` — the deployment guide documents the
alias workaround; compose.yml should probably gain a `phpfpm` network
alias
- The admin Users form still offers a `CTL` user type
(`frontend/src/admin/entities.ts`) that `UserType::from()` rejects
- Jira Cloud OAuth 2.0: data model + admin form exist (#416), but the
runtime 3LO flow is not implemented yet (`JiraAuthenticationService` is
OAuth1-only) — ADR-017 records this honestly
CybotTM added a commit that referenced this pull request Jul 2, 2026
…Server/DC (#501)

## Summary

Implements the runtime half of the Jira Cloud plan started in #416
("PR-2..4" of the original series): TimeTracker now syncs worklogs
against **both** Jira Server/DC (OAuth 1.0a, unchanged) and **Jira
Cloud** (OAuth 2.0 authorization-code / 3LO), selected per ticket system
via the deployment type that #416 introduced.

## How it works

- **`JiraCloudApiService`** extends the existing service and swaps only
the transport/auth layer: 3LO authorize redirect, authorization-code
exchange, **rotating refresh tokens** with recorded expiry (via the
app's `ClockInterface`, frozen-time safe), automatic **cloudId
resolution** from `accessible-resources` (matched by site host),
Bearer-authenticated REST through
`api.atlassian.com/ex/jira/{cloudId}/rest/api/2/`, and the Cloud-only
**`search/jql`** endpoint. All worklog/issue/subticket operations of the
base class run unchanged on top.
- **`CloudOAuthStateCodec`**: Cloud redirect URIs must match the
registered URL exactly, so the ticket-system id cannot ride as `?tsid=`
like in the OAuth1 flow — it travels inside the encrypted, user-bound,
10-minute-TTL `state` parameter instead.
- **`JiraOAuthApiFactory`** branches on `DeploymentType` — every
existing caller (worklog subscriber, subticket sync, exports, admin
sync) gets the right transport transparently.
- The shared **`/jiraoauthcallback`** route serves both flows (a `state`
parameter selects the Cloud branch; `error=access_denied` is reported
cleanly).
- REST **v2** is pinned deliberately: v3 requires Atlassian Document
Format for worklog comments; v2 keeps accepting the plain-text comments
the app sends.

Docs: ADR-017 updated to "implemented" with the mechanism recorded;
Cloud app-registration walkthrough (scopes, exact callback URL) added to
`docs/configuration.md`; README feature bullet updated.

Context: the Mogic fork's Cloud adaptation (mogic-le/timetracker,
TIM-130) confirmed `search/jql` as the required Cloud search endpoint;
their fork still uses deprecated OAuth 1.0a against Cloud — this PR
provides the supported 3LO path instead.

## Verification

- 21 new unit tests: state codec (round-trip, tamper, cross-key, TTL
expiry), authorize-URL construction with decodable state, Bearer client
against the gateway URL, token refresh & rotation incl. rejected-refresh
cleanup + re-authorize, cloudId host matching (case-insensitive) and
no-match failure, malformed token responses, `search/jql` payload,
OAuth1-callback rejection, bare callback URL
- Full unit suite: **1575 tests green**
- PHPStan level 10 clean, PHPat clean, PER-CS clean

## Out of scope

- A real end-to-end test against a live Atlassian app registration
(needs org credentials — the same limitation the #416 plan noted for its
PR-2)
- The dormant split-stack services (`JiraIntegrationService` etc.)
remain excluded from DI, unchanged
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.

2 participants