fix(jira): tag staging feeders 'staging' so silver builds on a fresh install (#1893) - #1895
Conversation
…install Unlike other connectors — which build via the legacy single pass `tag:<connector>+` that pulls every connector-tagged model plus its descendants — the jira pipeline builds in three narrow tag-scoped passes with no `tag:jira` catch-all: staging: tag:staging,tag:jira (render_cronworkflow/sync_trigger) silver: tag:silver,tag:jira+ (jira descriptor dbt_select) gold: tag:gold,tag:jira+ (jira descriptor dbt_select) Seven jira models were tagged only ['jira'] (or ['jira','silver:class_*'], where silver:class_* is a routing tag, not the bare 'silver' tag), so they matched none of these passes and were never materialized on a real sync: jira__users_snapshot jira__task_comments jira__task_projects jira__task_sprints jira__task_statuses jira__task_users jira__task_worklogs Their silver consumers then fail. jira__users_fields_history ref()s jira__users_snapshot and dies with `code: 60 Unknown table 'staging.jira__users_snapshot'` (the reported constructorfabric#1893 crash). The class_task_* silver models union their jira feeders via union_by_tag, which on a fresh install raises the "no source tables … target not materialised" compiler error, and on a mixed tenant silently drops all jira rows — so no jira task metrics are produced. Add the 'staging' tag to all seven (matching the already-correct jira__issue_field_snapshot / jira__task_field_metadata) so the staging pass materializes them before enrich and silver. jira__task_field_history is intentionally left alone: it is ephemeral, has no DB relation, and is inlined into its consumers via ref(), so no pass needs to build it. youtrack is NOT affected despite mirroring these models: it has no enrich step, so its pipeline uses the legacy `tag:youtrack+` pass which already pulls in every youtrack-tagged model. Reproduced on ClickHouse 25.7.5: pre-fix, building jira__users_fields_history against MergeTree bronze reproduces the exact `code: 60` error; post-fix, the staging pass materializes jira__users_snapshot and silver builds cleanly. Add a manifest-level regression test asserting every non-ephemeral jira model is selected by at least one prod pass (a metric e2e test cannot cover this: the rig builds `+<staging>` which masks the gap by pulling ancestors). Fixes constructorfabric#1893 Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
📝 WalkthroughWalkthroughJira dbt models now include the ChangesJira staging coverage
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/tests/e2e/meta/test_dbt_runner.py`:
- Around line 141-144: Update the path substring checked by _is_jira_model to
remove the leading slash, matching dbt-project-relative paths under
connectors/task-tracking/jira/dbt/ while preserving the existing resource_type
model check.
🪄 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 Plus
Run ID: 4a68cd48-b052-417e-8bce-fd2f298d3774
📒 Files selected for processing (8)
src/ingestion/connectors/task-tracking/jira/dbt/jira__task_comments.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__task_projects.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__task_sprints.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__task_statuses.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__task_users.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__task_worklogs.sqlsrc/ingestion/connectors/task-tracking/jira/dbt/jira__users_snapshot.sqlsrc/ingestion/tests/e2e/meta/test_dbt_runner.py
| def _is_jira_model(node: dict) -> bool: | ||
| return node.get("resource_type") == "model" and ( | ||
| "/connectors/task-tracking/jira/dbt/" in node.get("original_file_path", "") | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -a '^manifest\.json$' . -E target -E node_modules | while read -r manifest; do
echo "== $manifest"
jq -r '.nodes[]?.original_file_path // empty' "$manifest" |
grep 'task-tracking/jira/dbt/' | head -5
doneRepository: constructorfabric/insight
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate test file =="
fd -a 'test_dbt_runner.py$' . | sed 's#^\./##'
FILE="$(fd 'test_dbt_runner.py$' src/ingestion/tests/e2e/meta -1 || true)"
if [ -n "${FILE:-}" ]; then
echo "== outline for $FILE =="
ast-grep outline "$FILE" || true
echo "== relevant lines =="
sed -n '110,170p' "$FILE" | cat -n
fi
echo "== dbt-related files =="
fd -a 'dbt_project\.ya?ml|manifest\.json|model' . -E target -E node_modules -E .venv | sed 's#^\./##' | head -200
echo "== Jira connector references =="
rg -n 'task-tracking/jira/dbt|connectors/task-tracking/jira/dbt|/connectors/task-tracking/jira/dbt' . -E target -E node_modules -E .venv || trueRepository: constructorfabric/insight
Length of output: 4476
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== test full context =="
sed -n '1,120p' src/ingestion/tests/e2e/meta/test_dbt_runner.py | cat -n
echo "== dbt_project.yml =="
cat -n src/ingestion/dbt/dbt_project.yml
echo "== files under ingestion/dbt containing jira or task-tracking =="
find src/ingestion/dbt -maxdepth 6 \( -type f -o -type d \) | grep -E 'jira|task-tracking' | sort | sed -n '1,200p'
echo "== search repository (no binary) for jira paths =="
python3 - <<'PY'
import os, re
root='src/ingestion/dbt'
for dirpath, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for f in files:
if f.startswith('.'):
continue
p=os.path.join(dirpath,f)
if not os.path.isfile(p):
continue
try:
text=open(p,'r',encoding='utf-8',errors='ignore').read()
except Exception as e:
print(f"SKIP {p}: {e}")
continue
if 'jira' in text.lower() or 'task-tracking' in text:
print(p)
PY
echo "== dbt package/source references =="
python3 - <<'PY'
import os, re
root='src/ingestion/dbt'
pattern=re.compile(r'connectors/task-tracking/jira/dbt|/connectors/task-tracking/jira/dbt|task-tracking/jira/dbt', re.I)
for dirpath, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for f in files:
if f.startswith('.'): continue
p=os.path.join(dirpath,f)
try:
s=open(p,'rb').readline(1000)
except Exception:
continue
if b'connections' in s.lower() or b'jira' in s.lower() or b'task-tracking' in s.lower() or b'model' in s.lower():
try:
txt=open(p,'r',encoding='utf-8',errors='ignore').read()
except Exception:
continue
if pattern.search(txt):
for i,line in enumerate(txt.splitlines(),1):
if pattern.search(line):
print(f"{p}:{i}:{line}")
PYRepository: constructorfabric/insight
Length of output: 11111
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== all files under src/ingestion/dbt that reference task-tracking/jira/dbt or jira models =="
python3 - <<'PY'
import os, re
def ok(path):
root = 'src/ingestion/dbt'
return path.startswith(root + os.sep) and not any(part.startswith(('.', 'node_modules', 'target', '.venv', '__pycache__')) for part in path.split(os.sep))
root = 'src/ingestion/dbt'
patterns = [
re.compile(r'connectors/task-tracking/jira/dbt'),
re.compile(r'/connectors/task-tracking/jira/dbt'),
re.compile(r'jira__', re.I),
re.compile(r'task-tracking/jira/dbt', re.I),
]
matches = {}
for dirpath, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ('node_modules', 'target', '.venv', '__pycache__')]
for f in files:
if f.startswith('.'):
continue
path = os.path.join(dirpath, f)
try:
text = open(path, 'r', encoding='utf-8', errors='ignore').read()
except Exception as e:
print(f"SKIP {path}: {e}")
continue
for p in patterns:
if not p.search(text):
continue
matches.setdefault(path, []).append(p.pattern)
break
for path in sorted(matches):
print(f"== {path} ==")
lines = open(path, 'r', encoding='utf-8', errors='ignore').readlines()
hits = [(i+1, line.rstrip('\n')) for i, line in enumerate(lines) if any(re.search(p, line) for p in matches[path])]
for line_no, line in hits[:40]:
print(f"{line_no}: {line[:300]}")
PY
echo "== files under entire repo that reference Jira path pattern =="
rg -n 'connectors/task-tracking/jira/dbt|/connectors/task-tracking/jira/dbt' -g '!target/**' -g '!node_modules/**' -g '!dist/**' -g '!build/**' . || true
echo "== test after current snippet from src/ingestion/tests/e2e/meta/test_dbt_runner.py =="
sed -n '120,150p' src/ingestion/tests/e2e/meta/test_dbt_runner.py | cat -nRepository: constructorfabric/insight
Length of output: 3968
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lock/package references to Jira dbt package =="
rg -n 'task-tracking/jira/dbt|jira/dbt|connectors/task-tracking/jira/dbt|src/ingestion/connectors/task-tracking/jira/dbt' . \
--glob '!target/**' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' --glob '!*.lock' --glob '!*.json' \
| sort -u || true
echo "== dbt config/package files =="
git ls-files | rg '(^|/)(dbt_project\.yml|packages\.yml|package-lock\.json|packages-config\.yml|dbt_project_exclude|packages\.yml)$' | sed -n '1,200p'
echo "== src/ingestion/connectors task-tracking entries =="
find src/ingestion/connectors -maxdepth 3 -type f -o -type d | sort | sed -n '1,200p'Repository: constructorfabric/insight
Length of output: 10010
Avoid the leading slash in the Jira-model check.
original_file_path is dbt-project-relative for this package, so paths under the Jira connector are connectors/task-tracking/jira/dbt/.... Keeping / makes the filter miss Jira models entirely.
Proposed fix
- "/connectors/task-tracking/jira/dbt/" in node.get("original_file_path", "")
+ "connectors/task-tracking/jira/dbt/" in node.get("original_file_path", "")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _is_jira_model(node: dict) -> bool: | |
| return node.get("resource_type") == "model" and ( | |
| "/connectors/task-tracking/jira/dbt/" in node.get("original_file_path", "") | |
| ) | |
| def _is_jira_model(node: dict) -> bool: | |
| return node.get("resource_type") == "model" and ( | |
| "connectors/task-tracking/jira/dbt/" in node.get("original_file_path", "") | |
| ) |
🤖 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/tests/e2e/meta/test_dbt_runner.py` around lines 141 - 144,
Update the path substring checked by _is_jira_model to remove the leading slash,
matching dbt-project-relative paths under connectors/task-tracking/jira/dbt/
while preserving the existing resource_type model check.
Summary
Fixes #1893 — on a fresh install the jira silver dbt step fails, so no jira/task metrics build. The reported crash is
Database Error in model jira__users_fields_history … code: 60 … Unknown table expression identifier 'staging.jira__users_snapshot'.This is the same root cause as #1886 (merged in #1889), on more models.
Root cause
Unlike other connectors — which build via the legacy single pass
tag:<connector>+(pulls every connector-tagged model plus its descendants) — the jira pipeline builds in three narrow tag-scoped passes with notag:jiracatch-all:tag:staging,tag:jirarender_cronworkflow.py/render_sync_trigger.py(no+)tag:silver,tag:jira+descriptor.yamldbt_selecttag:gold,tag:jira+descriptor.yamldbt_selectSeven jira models were tagged only
['jira'](or['jira','silver:class_*']— notesilver:class_*is a routing tag, not the baresilvertag), so they match none of these passes and are never materialized on a real sync:Their silver consumers then fail:
jira__users_fields_historyref()sjira__users_snapshot→ hardcode: 60crash (the reported bug).class_task_*silver models union their jira feeders viaunion_by_tag, which on a fresh install raises the "no source tables … target not materialised" compiler error, and on a mixed tenant silently drops all jira rows → no jira task metrics.Fix
Add the
stagingtag to all seven (matching the already-correctjira__issue_field_snapshot/jira__task_field_metadata) so the staging pass materializes them before enrich and silver. They already carry-- depends_on: jira__bronze_promoted, so ordering is preserved.jira__task_field_historyis intentionally left alone: it is ephemeral, has no DB relation, and is inlined into its consumers viaref(), so no pass needs to build it standalone.Scope check (other connectors)
Audited every connector with
dbt lsagainst its real pipeline selector:jira__task_field_history— correct.tag:youtrack+pass, which already pulls in every youtrack-tagged model. Confirmed viadbt ls --select tag:youtrack+.tag:<connector>+→ no gaps.Verification (ClickHouse 25.7.5)
bronze_jira.jira_useras MergeTree, builtjira__users_fields_history→ reproduced the exactcode: 60 … Unknown table 'staging.jira__users_snapshot'.jira__bronze_promoted→jira__users_snapshot(dedup via bronzeFINAL), thenjira__users_fields_historybuilds →Completed successfully.Regression test
Added
test_every_jira_model_is_built_by_some_prod_pass: parses the manifest, replays the three prod passes (tag intersection ++descendants), and asserts every non-ephemeral jira model is selected by at least one. Validated: fails pre-fix (lists the 7 gaps), passes post-fix. A metric e2e test can't cover this — the rig builds+<staging>which masks the gap by pulling ancestors.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests