Skip to content

feat: Jira row on the project page (WP-C) - #247

Merged
selluxhenner merged 3 commits into
mainfrom
feat/jira-lead-ui
Oct 29, 2026
Merged

selluxhenner merged 3 commits into
mainfrom
feat/jira-lead-ui

Conversation

@selluxhenner

Copy link
Copy Markdown
Collaborator

WP-C of docs/plans/2026-10-29_jira-in-app.md: the project lead's Jira UI. Builds on the WP0 contract (#245, merged).

Jira row in the tools panel (jiraRowOf in features/projects/views.ts, pure and unit-tested):

  • company Jira off: label Link, hint "Your admin can switch the Jira sync on" (fixes the false "Two-way sync" claim)
  • on, project not sent: Send to Jira ("Creates an epic in NUP and N issues"), appends project.sync {system:"jira", on:true}
  • synced: chip "Sending…", "Synced · NUP-1 · checked 1 min ago" or "Sync problem: …", plus Stop syncing (on:false)
  • Link an existing issue: key field (isIssueKey) + step picker, appends human project.linked with the URL from jiraIssueUrl
  • step cards and the step sheet show the key chip, or "waiting for Jira"
  • buttons only for lead or manager (same mayAppendProject rule as the server)
  • the top "Sync" chip now only shows for a project that is actually synced

Data: the [company] project page reads getIntegration + jiraStatusView on the server and passes the plain JiraStatusView (no secrets) to ProjectView. Demo / no database gives null.

Demo: with null in the local demo the row shows Jira as on (NUP, host jira.demo.invalid), and act.project.sync plays the connector: it appends the same project.linked events (epic, then one per step), no network.

Tests: tests/unit/jira-row.test.ts (new), two new demo e2e tests in projects.spec.ts (send, link, stop; employee gets no buttons). Typecheck, lint, 855 unit tests, and the full demo suite (30 tests, run on Edge) pass.

No dependency, schema, .github/ or engine/admin file touched.

@selluxhenner
selluxhenner merged commit 9c3c915 into main Oct 29, 2026
5 checks passed
@selluxhenner
selluxhenner deleted the feat/jira-lead-ui branch October 29, 2026 01:57
selluxhenner added a commit that referenced this pull request Oct 29, 2026
WP-A of `docs/plans/2026-10-29_jira-in-app.md`: the Jira sync engine,
moved out of n8n into the app. Builds on the contract (#245), the admin
page (#246) and the lead UI (#247), which are all on main.

**What it does**
- `server/jira-sync.ts`: a loop once a minute (`JIRA_SYNC_INTERVAL_MS`
overrides it, the e2e run uses 2000) for each company with Jira on. It
takes the lease, pushes, then pulls, and records the result on the
Integration row. An hourly reconcile reads every linked issue.
`kickJira(slug)` starts a round early; it is called after a person's
project event (and a `case.decided`), and from the webhook.
- `server/jira/round.ts`: one round. It only knows a `RoundStore`, so
the unit tests run whole rounds against the fake Jira with an in-memory
log.
- Pure core in `features/integrations/jira/`: `map` (issue key to
project/step, sync mode), `plan` (state and events to jobs), `pull`
(Jira JSON to events), `status` (transition picking, hopping through In
Progress when the workflow has no direct way), `adf`.
- `server/jira/cloud.ts` (API v3, Basic auth, `/search/jql` with
`nextPageToken`, one inline wait on a short 429), `dc.ts` (stub that
says "not supported yet"), `test.ts` (`testJira`, a failed call is a
failed check).
- `POST /api/[company]/integrations/jira/webhook`: HMAC check, then only
`kickJira`. The header name (`x-hub-signature`) is marked TBD in
`server/jira/webhook.ts`; confirm it on the live site.
- One-line hooks in `instrumentation.ts` and `server/actions/events.ts`.

**Echo-loop rules, as built**
1. Push acts only on human actors (`isSystemActor`), and the last move
of a step in a batch decides.
2. Pull skips changes whose latest history is by the integration
account, and writes nothing when NextUp already shows the state (Blocked
against In Progress counts as the same).
3. idemKeys `jira:epic:<projectId>`, `jira:issue:<stepId>`,
`jira:<key>:status:<historyId>`, `jira:<key>:summary:<historyId>`,
`jira:added:<key>`, `jira:comment:<id>`. Creates look for the label
first and adopt an existing issue.
4. At most 20 creates per round. A round with nothing to do writes no
events and makes no write call to Jira (`jira-sync.test.ts` runs 5
silent rounds after each scenario).

**Decisions worth a look**
- The first round after switch-on does not replay old events (`pulledAt`
null and `pushedSeq` 0); creates come from the state, not the cursor.
- Imported comments read `Jira NUP-2: <text>` (cut to 2000), with no
author and mentions as `@someone`.
- A failed job that Jira refused for good (no transition, a 4xx) is
reported as `lastError` but does not hold the cursor; transient failures
(401, 429, 5xx, no answer) end the round and move nothing.
- Added beyond the file list: `server/jira/round.ts` and
`server/jira/webhook.ts`, so the engine and the HMAC check are testable
without a database.
- Not covered here: the e2e-db spec and the live test on
`nextup-test.atlassian.net`; and the Labels check in "Test connection"
can only prove the create/edit permission and that JQL on labels parses,
not that the create screen has the labels field.

**Run the fake Jira:** `node tests/fake-jira/server.mjs --port 4010`
(Node 22.18 or newer). It also takes `POST
/__fake/{move,rename,comment,child,429}`.

Checked: typecheck, eslint, `npm test` (88 files, 945 tests). `next
build` was not run.
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