feat(data-warehouse): onboard teams via duckgres org-teams api - #72302
Conversation
Switch managed-warehouse provisioning to the duckgres org-teams API: provision sends team_id + schema_name (no default_team_id), new list/create/delete team adapter functions, a team onboarding flow for additional projects, lazy grandfathering of existing Django-only teams, and team deletion blocking when duckgres refuses to drop its last team.
🤖 CI report✅ Bundle size — 🟢 -72.4 KiB (-0.1%)Uncompressed size of every built Total: 64.16 MiB · 🟢 -72.4 KiB (-0.1%)
Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 281.3 KiB | ../node_modules/.pnpm/posthog-js@1.404.1/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 223.4 KiB | ../node_modules/.pnpm/posthog-js@1.404.1/node_modules/posthog-js/dist/module.js |
| 164.0 KiB | src/queries/validators.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.9 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 93.2 KiB | ../packages/quill/packages/quill/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — 🔺 +1.23 MiB (+0.1%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1344.09 MiB · 🔺 +1.23 MiB (+0.1%)
ℹ️ MCP UI apps size — 31 app(s), 16603.4 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 599.5 KB | 187.4 KB |
| action | 457.8 KB | 187.4 KB |
| action-list | 564.3 KB | 187.4 KB |
| cohort | 456.8 KB | 187.4 KB |
| cohort-list | 563.3 KB | 187.4 KB |
| email-template | 456.6 KB | 187.4 KB |
| error-details | 472.4 KB | 187.4 KB |
| error-issue | 457.5 KB | 187.4 KB |
| error-issue-list | 564.2 KB | 187.4 KB |
| experiment | 561.5 KB | 187.4 KB |
| experiment-list | 565.1 KB | 187.4 KB |
| experiment-results | 563.2 KB | 187.4 KB |
| feature-flag | 567.1 KB | 187.4 KB |
| feature-flag-list | 570.9 KB | 187.4 KB |
| feature-flag-testing | 461.0 KB | 187.4 KB |
| insight-actors | 562.1 KB | 187.4 KB |
| invite-email-preview | 456.0 KB | 187.4 KB |
| llm-costs | 559.5 KB | 187.4 KB |
| session-recording | 458.6 KB | 187.4 KB |
| session-summary | 463.9 KB | 187.4 KB |
| survey | 458.4 KB | 187.4 KB |
| survey-global-stats | 562.2 KB | 187.4 KB |
| survey-list | 565.0 KB | 187.4 KB |
| survey-stats | 562.2 KB | 187.4 KB |
| trace-span | 457.2 KB | 187.4 KB |
| trace-span-list | 564.2 KB | 187.4 KB |
| workflow | 457.1 KB | 187.4 KB |
| workflow-list | 563.7 KB | 187.4 KB |
| query-results | 745.1 KB | 187.4 KB |
| render-ui | 825.7 KB | 187.4 KB |
| visual-review-snapshots | 461.6 KB | 187.4 KB |
⚠️ Backend coverage — 96.0% of changed backend lines covered — 16 uncovered
🧪 Backend test coverage
Patch coverage — changed backend lines (products + core): ███████████████████░ 96.0% (407 / 423)
| File | Patch | Uncovered changed lines |
|---|---|---|
posthog/ducklake/common.py |
65.0% | 588–594 |
products/data_warehouse/backend/presentation/views/data_warehouse.py |
75.0% | 894, 897, 1015 |
products/data_warehouse/backend/presentation/views/managed_warehouse.py |
95.0% | 410, 450, 452, 490–491, 589 |
🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 29756980746 -n patch-coverage), or the coverage-data block at the end of this comment.
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.7% |
8,416 / 21,225 |
demo |
███████████░░░░░░░░░ 56.2% |
1,497 / 2,663 |
warehouse_sources_queue |
████████████░░░░░░░░ 59.2% |
148 / 250 |
tasks |
█████████████░░░░░░░ 67.1% |
26,193 / 39,053 |
data_tools |
██████████████░░░░░░ 70.0% |
63 / 90 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
████████████████░░░░ 79.4% |
19,499 / 24,561 |
data_modeling |
████████████████░░░░ 80.0% |
4,834 / 6,045 |
cdp |
████████████████░░░░ 80.7% |
3,118 / 3,864 |
wizard |
████████████████░░░░ 82.5% |
772 / 936 |
notebooks |
█████████████████░░░ 85.1% |
7,096 / 8,338 |
cohorts |
█████████████████░░░ 86.2% |
4,065 / 4,717 |
agent_platform |
█████████████████░░░ 86.4% |
3,807 / 4,405 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
product_tours |
█████████████████░░░ 87.5% |
1,266 / 1,447 |
exports |
██████████████████░░ 88.4% |
6,943 / 7,853 |
business_knowledge |
██████████████████░░ 88.5% |
4,400 / 4,969 |
conversations |
██████████████████░░ 89.1% |
16,372 / 18,371 |
mcp_analytics |
██████████████████░░ 89.2% |
2,514 / 2,819 |
dashboards |
██████████████████░░ 89.4% |
5,917 / 6,616 |
visual_review |
██████████████████░░ 89.4% |
5,818 / 6,505 |
engineering_analytics |
██████████████████░░ 89.5% |
5,507 / 6,154 |
alerts |
██████████████████░░ 89.9% |
4,054 / 4,508 |
error_tracking |
██████████████████░░ 90.1% |
9,757 / 10,828 |
early_access_features |
██████████████████░░ 90.1% |
1,031 / 1,144 |
streamlit_apps |
██████████████████░░ 90.4% |
2,501 / 2,767 |
slack_app |
██████████████████░░ 90.6% |
8,989 / 9,926 |
links |
██████████████████░░ 90.6% |
183 / 202 |
marketing_analytics |
██████████████████░░ 90.8% |
11,514 / 12,684 |
stamphog |
██████████████████░░ 91.0% |
3,993 / 4,387 |
product_analytics |
██████████████████░░ 91.4% |
5,781 / 6,328 |
mcp_store |
██████████████████░░ 91.8% |
3,685 / 4,012 |
data_warehouse |
███████████████████░ 92.6% |
19,145 / 20,683 |
notifications |
███████████████████░ 92.7% |
1,031 / 1,112 |
ai_observability |
███████████████████░ 92.7% |
14,921 / 16,093 |
workflows |
███████████████████░ 92.8% |
5,520 / 5,949 |
web_analytics |
███████████████████░ 92.9% |
13,853 / 14,913 |
surveys |
███████████████████░ 93.0% |
5,724 / 6,157 |
posthog_ai |
███████████████████░ 93.2% |
1,325 / 1,421 |
approvals |
███████████████████░ 93.3% |
3,395 / 3,640 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
tracing |
███████████████████░ 93.4% |
2,546 / 2,725 |
managed_migrations |
███████████████████░ 93.8% |
1,220 / 1,300 |
legal_documents |
███████████████████░ 94.1% |
1,568 / 1,667 |
endpoints |
███████████████████░ 94.1% |
8,606 / 9,143 |
messaging |
███████████████████░ 94.2% |
2,647 / 2,810 |
revenue_analytics |
███████████████████░ 94.5% |
3,598 / 3,809 |
skills |
███████████████████░ 94.5% |
2,881 / 3,049 |
review_hog |
███████████████████░ 94.6% |
6,806 / 7,191 |
logs |
███████████████████░ 95.4% |
9,915 / 10,396 |
experiments |
███████████████████░ 95.7% |
24,417 / 25,527 |
replay_vision |
███████████████████░ 95.8% |
13,850 / 14,459 |
growth |
███████████████████░ 95.8% |
2,837 / 2,960 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
feature_flags |
███████████████████░ 96.3% |
16,233 / 16,859 |
user_interviews |
███████████████████░ 96.4% |
2,242 / 2,325 |
warehouse_sources |
███████████████████░ 96.5% |
289,438 / 300,067 |
access_control |
███████████████████░ 96.8% |
849 / 877 |
customer_analytics |
███████████████████░ 97.2% |
7,482 / 7,700 |
data_catalog |
███████████████████░ 97.4% |
2,303 / 2,365 |
analytics_platform |
████████████████████ 98.0% |
2,102 / 2,145 |
metrics |
████████████████████ 98.2% |
2,491 / 2,536 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
🦔 Hogbox preview · ✅ ready▶ Open the preview
commit |
Prompt To Fix All With AIFix the following 4 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 4
products/data_warehouse/backend/presentation/views/managed_warehouse.py:274-277
**Provisioning Can Skip Django Registration**
If `enable_team_backfill` fails after Duckgres accepts provisioning, this exception is swallowed and provisioning still succeeds. Status then finds the Duckgres team and reports it as onboarded, but the missing `DuckgresServerTeam` row means the Dagster backfill never runs and this path does not recreate it.
### Issue 2 of 4
products/data_warehouse/backend/presentation/views/managed_warehouse.py:410-417
**Dual Write Leaves Missing Backfill State**
The Duckgres upsert has already succeeded when this Django write runs. If the write loses a race or raises a database error, the request fails with a Duckgres-only team; later status reads report that team as onboarded but never create the `DuckgresServerTeam` row required by Dagster.
### Issue 3 of 4
frontend/src/scenes/data-warehouse/scene/warehouseProvisioningLogic.ts:523-528
**Prefilled Schema Never Gets Checked**
`afterMount` sets the default schema before the status response can make `needsTeamOnboarding` true, so this branch skips its availability request. When an existing warehouse then reports that the team needs onboarding, the valid prefilled name remains at `schemaNameAvailable === null` and the enable button stays disabled until the user edits the name.
### Issue 4 of 4
frontend/src/scenes/data-warehouse/scene/warehouseProvisioningLogic.ts:532-542
**Stale Checks Clear Current State**
Only the success assignment checks that the response still belongs to the current name. If a request for an older name fails after a newer request succeeds, its catch resets the newer name's availability to `null`, and either stale completion can clear `schemaNameChecking` while another request remains active.
Reviews (1): Last reviewed commit: "fix(data-warehouse): canonical kea-typeg..." | Re-trigger Greptile |
…lity checks Address review findings on the team-onboarding flow: - A duckgres team row without a Django backfill row (provision registered the team with the control plane but enable_team_backfill failed, or onboard's Django write raced) is now healed on the next status read: the Django row is recreated from the duckgres schema so Dagster picks the team up instead of it reporting onboarded forever with no backfill. Guarded to rows without legacy table-name overrides, where schema name and table suffix are the same identifier by construction. - The prefilled default schema name now gets its availability checked when the status response flips the scene into team onboarding (the prefill runs on mount, before needsTeamOnboarding can be true, so its check never fired). - Availability checks guard every state write on the request's name still being current, so a stale completion (success or failure) can no longer clobber a newer check's result or clear its spinner.
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
…x stale admin tests The schema availability check scans every project's schema in the org, so a non-admin could probe candidate names and learn what otherwise inaccessible projects use. Gate it behind the same organization-admin check as onboarding, and show non-admins an ask-an-admin state instead of the onboarding form. Also update the Django admin and access-control tests that still exercised the old surface (table_name field, removed enable_backfill adapter).
Problem
The managed warehouse (/data-ops) is org-scoped but team membership only lived in Django (
DuckgresServerTeam). The duckgres control plane now tracks teams first-class via its org-teams API, including per-team schemas and the billing team. The app needs to speak that API so a second project in an org can onboard itself with its own schema, existing teams get grandfathered into the control plane, and deleting a project keeps duckgres consistent.Note
Deploy ordering: this PR requires the duckgres org-teams API (
GET/POST /orgs/{org}/teams,DELETE /orgs/{org}/teams/{team_id}, provision acceptingteam_id+schema_name) to be deployed first. This PR sends nodefault_team_idanywhere anymore.Changes
Adapter (
products/data_warehouse/backend/presentation/views/managed_warehouse.py):list_teams,create_team,delete_teamproxy the new org-teams endpoints.create_teamomits unset legacy table-name fields so duckgres derives the<schema>.eventslayout for new teams.provisionnow sendsteam_id+schema_namefor the calling team instead ofdefault_team_id. The user-facing "table name" pick became the "schema name" pick.onboard_teamdual-writes: Django guards run first (the duckgres upsert would overwrite an existing row's schema), then the duckgres team row, then the Django backfill row that Dagster still reads. A duckgres 409 surfaces as a clear schema-conflict error.team_onboarding_statepowers the status response: it resolves whether the calling team has a duckgres row and lazily grandfathers Django-only teams into duckgres (schematable_suffixorteam_<id>, explicit legacy namesevents_<suffix>/events,persons_<suffix>/persons,posthog_data_imports_<suffix>/posthog_data_imports_team_<id>, matching the duckling DAG and the data-imports schema helper). Best-effort and idempotent, never fails the status read.check_schema_namechecks a candidate schema against both duckgres rows and Django-only suffixes (not yet grandfathered).block_team_deletionremoves the team from duckgres ahead of a Django team deletion. It only calls the control plane when the org has aDuckgresServerrow, blocks with guidance when duckgres 409s on its last team, and blocks with a retry error when the control plane is unreachable for a Django-onboarded team. Teams without warehouse involvement are unaffected.API (
data_warehouse.pyviewset):provisiontakesschema_name(wastable_name);enable_backfillis replaced byonboard-team; newcheck-schema-name;warehouse_statusgainsteam_onboarded+schema_name.Team deletion:
TeamViewSet.perform_destroyandProjectViewSet.perform_destroyconsultblock_team_deletionbefore dispatching the deletion workflow. For a project, environments already removed from duckgres before a block self-heal via the lazy grandfather push on the next status read.Frontend (
warehouseProvisioningLogic.ts+SettingsTab.tsx):Django admin: provision and enable-backfill forms now take a schema name; the enable-backfill view runs the same dual-write onboarding path.
flowchart TD S[warehouse_status] --> A{warehouse in duckgres?} A -- no --> P[Provision form: database name + schema name] A -- yes --> B{team onboarded?} B -- duckgres row --> N[Normal screen] B -- Django row only --> G[Lazy grandfather push to duckgres] --> N B -- no rows --> O[Onboarding screen: pick schema, live availability, onboard-team] classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff; classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000; classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000; class S,P,O phBlue; class N phYellow; class A,B,G phGray;How did you test this code?
Automated tests only (no manual run against a live control plane):
products/data_warehouse/backend/tests/api/test_managed_warehouse.py(46 passed): provision body carriesteam_id+schema_nameand neverdefault_team_id; org-teams proxy routes and upsert body shape (unset legacy fields omitted, which would otherwise flip a derived-layout team to explicit names); onboard-team dual-write, 409 surfacing, and Django-guard-before-control-plane ordering; onboarding-state branches including both grandfather mappings (suffix set and NULL) and control-plane failure fallbacks; schema-name availability against duckgres and Django-only rows; everyblock_team_deletionbranch (no warehouse, deleted/404, last-team 409, unreachable for onboarded vs not-onboarded team).posthog/ducklake/test_common.py(45 passed) covers theenable_team_backfillguard split (check_team_backfill_enable).posthog/api/test/test_team.py/test_project.pydeletion tests: new wiring guard that a warehouse block stops the deletion before the workflow dispatch, on both the environments and projects routes.warehouseProvisioningLogic.test.ts(11 passed) — onboarding branch detection, onboard success refresh, and 409 marking the schema name unavailable. No component-render tests added; the behavior lives in the kea logic.pnpm --filter=@posthog/frontend typescript:checkclean,hogli build:openapiregenerated types,hogli ci:preflight --fixclean.Automatic notifications
Docs update
No docs under
docs/cover the managed-warehouse onboarding flow yet.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored by Claude Code (Fable 5) directed by the assignee. Skills invoked: /improving-drf-endpoints and /writing-tests.
Decisions worth knowing about while reviewing:
enable_backfillendpoint is removed rather than kept as an alias; the scene is its only consumer and the admin now uses the onboarding path. A stale frontend would briefly 404 on it during rollout.events/personsfor NULL suffix,posthog_data_imports_team_<id>) were verified against the duckling backfill DAG andduckgres_data_imports_schemabefore hardcoding.