Skip to content

feat(jira): auto-discover projects — drop the jira_project_keys allowlist - #1316

Merged
mitasovr merged 1 commit into
constructorfabric:mainfrom
mitasovr:claude/jira-project-autodiscovery
Jun 15, 2026
Merged

feat(jira): auto-discover projects — drop the jira_project_keys allowlist#1316
mitasovr merged 1 commit into
constructorfabric:mainfrom
mitasovr:claude/jira-project-autodiscovery

Conversation

@mitasovr

@mitasovr mitasovr commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Supersedes #941 (original idea and per-project JQL by mozhaev-dev, mirrored from cyberfabric/cyber-insight#616) — reworked from scratch against the current manifest, since #941 predates #1283/#1308/#1310 and can no longer apply cleanly.

What

  • jira_project_discovery — inline-only parent under definitions (invisible to discover(), so reconcile/ADR-0015 cannot auto-select it as a bronze table): GET /rest/api/3/project/search?expand=insight enumerates every project visible to the API token.
  • Both jira_issue and jira_issue_keys are partitioned per project (project = "<KEY>" JQL). This is the critical delta vs [PR #616] feat(jira): auto-discover projects — remove mandatory jira_project_keys #941: it predates jira_issue_keys (fix(ingestion): stop silent jira sync hang via lightweight substream parent #1283) and, by deleting the config key its JQL still references, would have silently zeroed out jira_issue_history/jira_comments/jira_worklogs.
  • Incremental discovery gate: client-side cursor on the hoisted insight.lastIssueUpdateTime — first sync emits all projects; subsequent syncs only projects whose issues changed since the previous run. Comments/worklogs bump the issue's updated → the project aggregate, so one gate covers all substreams.
  • Timezone tail fixed safely: lookback_window PT1H → PT14H on the issue scans (JQL bare datetimes are evaluated in the API user's local TZ). [PR #616] feat(jira): auto-discover projects — remove mandatory jira_project_keys #941 instead pushed end_datetime 14h into the future — that advances the cursor past the actual query time and permanently skips records updated in between.
  • jira_project_keys removed everywhere (spec, descriptor required_fields, secret example, README).
  • descriptor 1.2.1 → 2.0.0 (major → reconcile dispatches full refresh; the per-project partitioning resets incremental state anyway — intended migration, per the chore(connectors): bump jira/confluence descriptor versions to publish #1308 #1310 lesson the bump ships in the same PR as the manifest change).

Verified live (virtuozzo Jira, isolated read runs)

Check Result
First read, no jira_project_keys in config ✅ Succeeded, 211 projects enumerated
Coverage vs old allowlist (4 projects) 25 projects had fresh issues; 2478 records — the allowlist would have missed 538 of them across 21 projects
Identity stamp unique_key = virtuozzo-jira-main-<KEY> intact
Parent gate state parent_state.jira_project_discovery.last_issue_update_time advances
Resume read with state ✅ only 3/211 project partitions touched (just those updated after the cursor) — idle projects cost zero requests
CDK runtime validate ✅ manifest valid
validate-strict 14 pre-existing errors on main (jira is the known whole-object-$ref anti-template) — count and nature unchanged by this diff

Known costs (documented in-manifest)

  • First sync after rollout = full re-sync of all visible projects since jira_start_date — plan for a long first run.
  • The client-side gate compares strictly against the stored cursor (empirically, lookback_window does not widen it on resume). If Atlassian's insight aggregate ever lags, that project's data is delayed until its next issue update pushes the aggregate past the cursor. Accepted: the aggregate was observed second-fresh on live Cloud; any full refresh re-covers everything.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Jira projects are now automatically discovered during each sync operation rather than requiring manual configuration.
  • Breaking Changes

    • Removed the jira_project_keys configuration parameter; projects are now auto-discovered based on available access.
  • Documentation

    • Updated documentation and configuration examples to reflect the new auto-discovery behavior and connector version 2.0.0.

…list

Replaces the manually-maintained jira_project_keys secret field with
runtime project discovery. Supersedes constructorfabric#941 (idea and per-project JQL by
mozhaev-dev, cyberfabric/cyber-insight#616), reworked against the current
manifest:

- New inline-only parent `jira_project_discovery` under `definitions`
  (never visible to discover, so reconcile cannot select it as a bronze
  table): GET /rest/api/3/project/search?expand=insight enumerates every
  project the API token can see.
- BOTH jira_issue and jira_issue_keys (the lightweight parent of
  jira_issue_history/comments/worklogs from constructorfabric#1283) are partitioned per
  project with JQL `project = "<KEY>"`. constructorfabric#941 predates jira_issue_keys and
  would have silently broken all three substreams by removing the config
  their JQL still referenced.
- Incremental discovery gate: client-side cursor on the hoisted
  insight.lastIssueUpdateTime — first sync emits all projects, later syncs
  only projects whose issues changed. Verified live: first read enumerated
  211 projects (25 with fresh issues, 2478 records — the 4-project
  allowlist would have missed 9 of them), resume read touched only 3/211
  partitions. Projects without issues default to epoch and stay filtered
  until their first issue.
- Timezone tail fix done safely: lookback_window PT1H -> PT14H on the
  issue scans. constructorfabric#941 instead pushed end_datetime 14h into the future, which
  advances the cursor past the actual query time and permanently skips
  records updated in between.
- jira_project_keys removed from spec, descriptor required_fields, secret
  example and README.
- descriptor 1.2.1 -> 2.0.0 (major): per-project partitioning resets
  incremental state; reconcile dispatches a full refresh on major bumps,
  which is the intended migration.

Known costs, documented in-manifest: the first sync after rollout is a
full re-sync of every visible project since jira_start_date; the
client-side gate compares strictly against the cursor (lookback does not
widen it on resume), so a lagging insight aggregate delays that project
until its next update.

validate (CDK runtime): manifest valid. validate-strict: 14 pre-existing
errors on main (jira is the known whole-object-$ref anti-template);
unchanged by this diff.

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

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Jira connector refactors project scoping from manual jira_project_keys configuration to automatic runtime discovery via a new jira_project_discovery stream that feeds per-project partitions to both jira_issue and jira_issue_keys, with adjusted lookback windows and removed configuration requirement.

Changes

Jira Project Auto-Discovery Refactor

Layer / File(s) Summary
Project discovery stream definition
src/ingestion/connectors/task-tracking/jira/connector.yaml
A new jira_project_discovery incremental stream is introduced to enumerate projects visible to the configured API token via Jira's project search endpoint, using insight.lastIssueUpdateTime as the client-side cursor to skip idle projects.
Issue stream partition routing and cursor tuning
src/ingestion/connectors/task-tracking/jira/connector.yaml
jira_issue now partitions by project_key from the discovery stream using SubstreamPartitionRouter, replaces config['jira_project_keys'] with stream_partition.project_key in JQL, and extends the incremental lookback window from PT1H to PT14H with documentation about timezone evaluation.
Issue keys stream partition routing and cursor tuning
src/ingestion/connectors/task-tracking/jira/connector.yaml
jira_issue_keys gains per-project partition routing via the discovery stream, updates JQL to reference stream_partition.project_key, adjusts field requests to query updated, and widens the lookback window from PT1H to PT14H.
Configuration cleanup and versioning
src/ingestion/connectors/task-tracking/jira/connector.yaml, src/ingestion/connectors/task-tracking/jira/descriptor.yaml, src/ingestion/secrets/connectors/jira.yaml.example, src/ingestion/connectors/task-tracking/jira/README.md
Removes jira_project_keys from the connection spec's required fields and schema, removes it from secret required fields, bumps descriptor to version 2.0.0 with release notes, removes the commented example from the K8s Secret template, and updates README documentation to describe auto-discovery and incremental gating behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • constructorfabric/insight#1283: Both PRs modify jira_issue_keys stream wiring in the Jira connector (this PR adds discovery-based partitioning, while the referenced PR introduced an earlier change to that stream's architecture).

Suggested reviewers

  • mozhaev-dev

Poem

🐰 Hops of discovery, hopping so free,
No keys to constrain, let the projects we see!
Each sync finds new scope through the API's wide gate,
With lookback extended, no idle we wait,
Auto-discovery—a rabbit's delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: removing the manual jira_project_keys allowlist and implementing automatic project discovery.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/ingestion/connectors/task-tracking/jira/README.md (1)

14-15: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove outdated manual project-key prerequisite.

This still instructs users to manually choose project keys and says it is required, which conflicts with the new runtime auto-discovery behavior and can mislead setup.

Suggested doc fix
-3. Identify the project keys to sync (e.g. `TC`, `TNG`) — visible in any issue URL as the prefix before the hyphen. Jira Cloud rejects unbounded JQL queries, so this is **required**.
+3. Ensure the API token can browse all projects you want ingested. Project scope is auto-discovered at runtime and queried per project with bounded JQL.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/task-tracking/jira/README.md` around lines 14 - 15,
Update the README.md to remove or replace the outdated instruction "Identify the
project keys to sync (e.g. `TC`, `TNG`) — visible in any issue URL as the prefix
before the hyphen. Jira Cloud rejects unbounded JQL queries, so this is
**required**." with a note that project keys are auto-discovered at runtime (or
optional) by the connector; ensure the wording around project key selection
reflects the new runtime auto-discovery behavior and does not state it as a
required manual step so users are not misled.
src/ingestion/connectors/task-tracking/jira/connector.yaml (1)

8021-8025: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Register jira_issue_keys in autoImportSchema.

This stream is now documented as a real bronze table, but metadata.autoImportSchema still omits it while the other persisted Jira streams are listed there. If schema import is driven by that map, reconcile can select jira_issue_keys while the ingestion layer never auto-registers its schema.

Suggested fix
 metadata:
   autoImportSchema:
     jira_fields: true
     jira_projects: true
     jira_user: true
     jira_issue: true
+    jira_issue_keys: true
     jira_issue_history: true
     jira_comments: true
     jira_worklogs: true

Also applies to: 8026-8181, 8237-8249

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/task-tracking/jira/connector.yaml` around lines 8021
- 8025, The metadata.autoImportSchema map in connector.yaml omits the newly
promoted bronze stream jira_issue_keys, causing schema auto-registration to skip
it; update the metadata.autoImportSchema entry to include "jira_issue_keys"
alongside the other persisted Jira streams so the ingestion layer will
auto-register its schema (modify the autoImportSchema map where other jira
bronze tables are listed to add the jira_issue_keys key and ensure its metadata
aligns with the existing bronze entries).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/connectors/task-tracking/jira/connector.yaml`:
- Around line 128-143: The incremental cursor logic uses a single global
last_issue_update_time (cursor_field) in jira_project_discovery which causes
projects that become newly visible but have older aggregate timestamps to be
permanently skipped; update the gating so it is project-aware: change
jira_project_discovery and/or the DatetimeBasedCursor usage to either persist
per-project cursor state keyed by project_key or always emit partitions for
unseen project_key values regardless of the global last_issue_update_time, and
update the issue streams that consume last_issue_update_time to read
project-scoped partitions (or consult the per-project cursor) so backfills for
permission/visibility changes are emitted.

---

Outside diff comments:
In `@src/ingestion/connectors/task-tracking/jira/connector.yaml`:
- Around line 8021-8025: The metadata.autoImportSchema map in connector.yaml
omits the newly promoted bronze stream jira_issue_keys, causing schema
auto-registration to skip it; update the metadata.autoImportSchema entry to
include "jira_issue_keys" alongside the other persisted Jira streams so the
ingestion layer will auto-register its schema (modify the autoImportSchema map
where other jira bronze tables are listed to add the jira_issue_keys key and
ensure its metadata aligns with the existing bronze entries).

In `@src/ingestion/connectors/task-tracking/jira/README.md`:
- Around line 14-15: Update the README.md to remove or replace the outdated
instruction "Identify the project keys to sync (e.g. `TC`, `TNG`) — visible in
any issue URL as the prefix before the hyphen. Jira Cloud rejects unbounded JQL
queries, so this is **required**." with a note that project keys are
auto-discovered at runtime (or optional) by the connector; ensure the wording
around project key selection reflects the new runtime auto-discovery behavior
and does not state it as a required manual step so users are not misled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d0c452d-c3a4-49a9-9e1b-a72b3bfa6817

📥 Commits

Reviewing files that changed from the base of the PR and between 43bfb9f and d11ef06.

📒 Files selected for processing (4)
  • src/ingestion/connectors/task-tracking/jira/README.md
  • src/ingestion/connectors/task-tracking/jira/connector.yaml
  • src/ingestion/connectors/task-tracking/jira/descriptor.yaml
  • src/ingestion/secrets/connectors/jira.yaml.example
💤 Files with no reviewable changes (1)
  • src/ingestion/secrets/connectors/jira.yaml.example

Comment on lines +128 to +143
incremental_sync:
type: DatetimeBasedCursor
cursor_field: last_issue_update_time
is_client_side_incremental: true
cursor_datetime_formats:
- "%Y-%m-%dT%H:%M:%S.%f%z"
datetime_format: "%Y-%m-%dT%H:%M:%S.%f%z"
start_datetime:
type: MinMaxDatetime
datetime: "1970-01-01"
datetime_format: "%Y-%m-%d"
end_datetime:
type: MinMaxDatetime
datetime: "{{ now_utc().strftime('%Y-%m-%dT%H:%M:%S.%f%z') }}"
datetime_format: "%Y-%m-%dT%H:%M:%S.%f%z"
lookback_window: P3D

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Parent incremental state can permanently skip newly visible projects.

jira_project_discovery stores a single max last_issue_update_time, and both issue streams only read partitions that this parent re-emits. If the token later gains access to a project whose aggregate timestamp is already older than that cursor, that project never gets partitioned at all until another issue update happens there. That silently drops backfill for permission changes, restored visibility, renamed projects, and other “newly seen but not freshly updated” cases. Consider making the gate project-aware (emit unseen project_key values regardless of timestamp, or persist per-project state) instead of relying on one global timestamp cursor.

Also applies to: 879-887, 8067-8075

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/task-tracking/jira/connector.yaml` around lines 128
- 143, The incremental cursor logic uses a single global last_issue_update_time
(cursor_field) in jira_project_discovery which causes projects that become newly
visible but have older aggregate timestamps to be permanently skipped; update
the gating so it is project-aware: change jira_project_discovery and/or the
DatetimeBasedCursor usage to either persist per-project cursor state keyed by
project_key or always emit partitions for unseen project_key values regardless
of the global last_issue_update_time, and update the issue streams that consume
last_issue_update_time to read project-scoped partitions (or consult the
per-project cursor) so backfills for permission/visibility changes are emitted.

@mitasovr
mitasovr merged commit 3c97f18 into constructorfabric:main Jun 15, 2026
11 of 12 checks passed
mozhaev-dev added a commit that referenced this pull request Jun 15, 2026
Lesson applied from jira #1316: the incremental cursors on support_tickets and
zendesk_satisfaction_ratings had no lookback. Zendesk's incremental export is
eventually-consistent at the cursor boundary, so a record whose updated_at
lands at the edge is permanently skipped on the next sync — and for a ticket
that drops its entire audit activity (the only source of
updates/comments/solved). Added lookback_window: P1D to both cursors so each
sync re-queries a 1-day tail; append-only RMT bronze + read-time dedup absorb
the re-delivery (verified: bronze distinct stable 32/5/102, silver exact 36/36
across 4 syncs, zero duplicates).

NB the fix is lookback_window, NOT pushing end_datetime into the future (the
#941 approach #1316 flagged: that advances the cursor past the real query time
and permanently skips rows). descriptor 1.2.2 → 1.2.3 (patch: cursor state
stays valid, lookback only widens the start each run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
mitasovr pushed a commit to mitasovr/insight that referenced this pull request Jun 18, 2026
…nc every run

constructorfabric#1316 made jira_issue and jira_issue_keys substreams of
jira_project_discovery. The CDK then kept their cursor state per-partition
(keyed by project — 211 partitions, under the 10k cap that auto-switches
to a global cursor), and that per-partition state did NOT persist: the
connection state stored `{"states": []}` for both. Every sync therefore
re-pulled all projects from jira_start_date — ~2.5h and 2M+ records per
run, so the Argo poll timed out and the enrich/dbt steps never ran (jira
silver stale; bronze jira_issue frozen at the last fully-committed run).
Before constructorfabric#1316, jira_issue was a plain incremental stream and a nightly run
was ~80k records in ~3 min.

Fix: set `global_substream_cursor: true` on both streams so they use a
single max(updated) cursor that persists (the same mechanism the
history/comments/worklogs substreams already use automatically, since
their per-issue partition count exceeds the 10k cap).

Verified live (isolated read against virtuozzo Jira, full manifest +
auto-discovery):
- state now persists as
  {"use_global_cursor": true, "state": {"updated": "..."},
   "parent_state": {"jira_project_discovery": {...}}}
- first read (window from 2026-06-16): 5100 records
- resume read with that state: 235 records (was: full re-sync every time)

descriptor 2.0.0 -> 2.0.1 so reconcile republishes the manifest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
mitasovr added a commit that referenced this pull request Jun 18, 2026
…nc every run (#1370)

#1316 made jira_issue and jira_issue_keys substreams of
jira_project_discovery. The CDK then kept their cursor state per-partition
(keyed by project — 211 partitions, under the 10k cap that auto-switches
to a global cursor), and that per-partition state did NOT persist: the
connection state stored `{"states": []}` for both. Every sync therefore
re-pulled all projects from jira_start_date — ~2.5h and 2M+ records per
run, so the Argo poll timed out and the enrich/dbt steps never ran (jira
silver stale; bronze jira_issue frozen at the last fully-committed run).
Before #1316, jira_issue was a plain incremental stream and a nightly run
was ~80k records in ~3 min.

Fix: set `global_substream_cursor: true` on both streams so they use a
single max(updated) cursor that persists (the same mechanism the
history/comments/worklogs substreams already use automatically, since
their per-issue partition count exceeds the 10k cap).

Verified live (isolated read against virtuozzo Jira, full manifest +
auto-discovery):
- state now persists as
  {"use_global_cursor": true, "state": {"updated": "..."},
   "parent_state": {"jira_project_discovery": {...}}}
- first read (window from 2026-06-16): 5100 records
- resume read with that state: 235 records (was: full re-sync every time)

descriptor 2.0.0 -> 2.0.1 so reconcile republishes the manifest.

Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
SharedQA added a commit to SharedQA/insight that referenced this pull request Jun 22, 2026
…tion

The jira descriptor lost the jira_project_keys required field when constructorfabric#1316
replaced the allowlist with auto-discovery; that change reached this branch
via the main merge but the hardcoded expectation in
test_fallback_parses_real_jira_descriptor still listed four fields, so the
E2E suite failed on the stale assertion (the safe_load parity check passed).
Align the expected list with the current three-field descriptor.

Signed-off-by: Kenan Salim <kenan.salim@rolos.com>
@mitasovr mitasovr linked an issue Aug 14, 2026 that may be closed by this pull request
4 tasks
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.

Jira connector: no way to verify that a sync covered every project

2 participants