Skip to content

feat(data-warehouse): implement testrail import source - #69391

Merged
talyn-app[bot] merged 3 commits into
masterfrom
tom/dwh-testrail
Jul 9, 2026
Merged

feat(data-warehouse): implement testrail import source#69391
talyn-app[bot] merged 3 commits into
masterfrom
tom/dwh-testrail

Conversation

@Gilbert09

Copy link
Copy Markdown
Member

Problem

TestRail was scaffolded as a warehouse source but had no sync logic, so users couldn't pull their test management data (projects, test cases, runs, results) into the PostHog Data warehouse to analyze QA activity alongside product data.

Changes

Implements the TestRail source end-to-end following the standard source.py / settings.py / testrail.py split:

  • 13 tables: projects, users, suites, sections, cases, milestones, runs, plans, tests, results, plus the statuses / priorities / case_types dictionaries for joining ids to labels.
  • Resumable fan-out: TestRail's hierarchy (projects → suites → cases, runs → tests/results) is walked in deterministic ascending-id order with a (parent, phase, offset) resume cursor saved after each yielded page, so Temporal restarts pick up where they left off and merge dedupes the boundary page.
  • Incremental sync only where TestRail exposes a genuine server-side timestamp filter: cases (updated_after), runs / plans / results (created_after). Incremental endpoints declare sort_mode="desc" since fan-out rows never arrive globally time-ordered, so the watermark commits only at end of sync.
  • Plan-aware runs: get_runs excludes runs inside test plans, so the runs table (and the run enumeration behind tests / results) also walks get_plansget_plan entries. The incremental caveat (runs added later to a pre-watermark plan need a full refresh) is documented in the source and the docs page.
  • Pagination safety: both response shapes (plain array and 6.7+ bulk envelope) are handled, and has_more comes exclusively from _links.next so an endpoint that ignores limit/offset can't loop forever.
  • Auth & safety: HTTP Basic (email + API key) over make_tracked_session(), subdomain validated as a single DNS label and declared in connection_host_fields so retargeting it re-requires the key, 401/403 mapped to non-retryable errors with TestRail's own error message surfaced (including the "API is disabled" case), 429/5xx retried with backoff.
  • Canonical table/column descriptions from the TestRail API docs, lists_tables_without_credentials = True so the public docs render the table catalog, SOURCES.md moved to Implemented.

Kept behind unreleasedSource=True with releaseStatus=ALPHA for now.

Note

I couldn't curl-verify endpoint behavior against a live instance (no TestRail credentials available; the URL scheme and error shapes were verified against a live instance unauthenticated, the rest against the current API docs). Where the docs were ambiguous the code is conservative: has_more never relies on page-size heuristics, and unverified filters degrade to a full re-pull that merge dedupes rather than data loss.

The user-facing doc (contents/docs/cdp/sources/testrail.md) is written and needs to land in the posthog.com repo; manage.py audit_source_docs passes for testrail against that checkout.

How did you test this code?

Automated tests only (no live TestRail instance available):

  • tests/test_testrail.py (transport): URL building (a second ? would break TestRail's router), subdomain retargeting rejection, both payload shapes and the infinite-loop guard on _links.next, retryable vs terminal status mapping, pagination checkpointing after yield, suite fan-out with updated_after on every request, resume skipping completed suites, runs combining standalone + plan-entry runs, results keeping run enumeration unfiltered while filtering per-run requests (catches new results in old runs), admin → per-project users fallback with dedupe, and credential validation surfacing TestRail's error body.
  • tests/test_testrail_source.py (source class): schema catalog incremental flags, connection_host_fields, non-retryable error matching, argument plumbing including dropping a stale watermark on full refresh.

Ran the full sources/tests/ registry suite (1353 passed) plus the 77 new tests; ruff check / ruff format clean; hogli ci:preflight --fix reports 0 failures.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

Doc written per the documenting-warehouse-sources skill; it lives in the posthog.com repo (companion PR) since this repo doesn't carry source docs. docsUrl matches the testrail slug.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored with Claude Code following the /implementing-warehouse-sources, /writing-tests, and /documenting-warehouse-sources skills, using recent sources (ruddr, secoda, aha, github) as references. Notable decisions:

  • Chose ResumableSource with a deterministic parent-walk cursor over per-endpoint next-URL state because TestRail pagination is offset-based and fan-out heavy.
  • Included plan-entry runs in the runs table (and run enumeration) rather than mirroring get_runs's standalone-only semantics, since plan-driven QA teams would otherwise get empty tests/results.
  • Declared sort_mode="desc" on incremental endpoints after checking the pipeline's watermark checkpointing semantics; asc would corrupt the watermark on mid-sync restarts given non-monotonic fan-out ordering.
  • generate_source_configs currently rewrites unrelated classes, so only the TestrailSourceConfig change was spliced into generated_configs.py.

Created with PostHog Code

Implements the TestRail warehouse source end-to-end: declarative endpoint
catalog (projects, users, suites, sections, cases, milestones, runs, plans,
tests, results, statuses, priorities, case_types), resumable limit/offset
pagination with a deterministic parent-walk cursor, server-side incremental
sync for cases/runs/plans/results, credential validation, canonical table
descriptions, and transport + source tests.

Generated-By: PostHog Code
Task-Id: 7dc7b3fb-650d-459e-851d-bc5685ddac7d
@Gilbert09 Gilbert09 self-assigned this Jul 8, 2026
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 8, 2026 16:23
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Hey @Gilbert09! 👋

It looks like your git author email on this PR isn't your @posthog.com address (owerstom@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat(data-warehouse): implement testrail..." | Re-trigger Greptile

Use direct ["id"] access on the users primary key during per-project dedupe
so a malformed response raises instead of silently collapsing keyless records
onto the None sentinel.

Generated-By: PostHog Code
Task-Id: 7dc7b3fb-650d-459e-851d-bc5685ddac7d
@trunk-io

trunk-io Bot commented Jul 8, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

Generated-By: PostHog Code
Task-Id: 885588aa-0211-42ce-a13a-2b3bd4a223e8
@talyn-app
talyn-app Bot merged commit b1a0f9e into master Jul 9, 2026
218 checks passed
@talyn-app
talyn-app Bot deleted the tom/dwh-testrail branch July 9, 2026 09:45
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-09 10:11 UTC Run
prod-us ✅ Deployed 2026-07-09 10:45 UTC Run
prod-eu ✅ Deployed 2026-07-09 10:49 UTC 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.

2 participants