Skip to content

Manual transform tasks now run via deployments (all of them) & remove the dependency tasks from the list - #1431

Merged
himanshudube97 merged 3 commits into
mainfrom
keep-core-manual-transform-tasks
Jul 14, 2026
Merged

Manual transform tasks now run via deployments (all of them) & remove the dependency tasks from the list#1431
himanshudube97 merged 3 commits into
mainfrom
keep-core-manual-transform-tasks

Conversation

@Ishankoradia

@Ishankoradia Ishankoradia commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Manual transformation deployments now create full prerequisite chains (git setup → dbt-clean → dbt-deps → the primary dbt step) and support chained long-running workflows.
    • Transformation task listings are now dataflow-chain aware and only show tasks the UI can render; automatically managed dependency tasks are kept hidden.
    • Added a backfill tool to migrate existing manual transformation deployments to the new chained format.
  • Bug Fixes
    • Secret storage now reliably uses the sanitized persisted block name from the scheduler.
    • Improved org cleanup to remove related preference/config records and delete transformation layers safely.
    • Added validation for missing or misconfigured transformation queue settings.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71f7f0f3-a4e9-4aee-8a6c-583599279e19

📥 Commits

Reviewing files that changed from the base of the PR and between 7c2d658 and 59df401.

📒 Files selected for processing (1)
  • ddpui/tests/services/dbt_service/test_update_github_pat_storage.py

Walkthrough

Manual dbt transform deployments now include ordered prerequisite chains, APIs return primary renderable tasks, a backfill command migrates existing deployments, secret names use persisted values, and organization cleanup handles additional related records.

Changes

Transform deployment flow

Layer / File(s) Summary
Dependency-aware transform API
ddpui/utils/constants.py, ddpui/api/data_api.py, ddpui/api/orgtask_api.py, ddpui/ddpdbt/dbt_service.py, ddpui/tests/api_tests/test_orgtask_api.py, ddpui/tests/services/dbt_service/*
Transform task listings exclude auto-managed dependencies, manual dataflows provide primary tasks and pipeline locks, secret persistence uses sanitized block names, and tests cover multiple long-running dbt tasks and persisted secret names.
Chained deployment orchestration
ddpui/core/orgtaskfunctions.py, ddpui/core/orchestrate/pipeline_service.py
Manual dbt deployments build git and dbt prerequisite chains, resolve the transform queue, persist ordered task mappings, and use renamed public orchestration helpers.
Dependency backfill command
ddpui/management/commands/backfill_manual_transform_tasks_dependencies.py
A management command updates existing manual deployments, creates missing deployments, supports organization filtering and dry runs, and reports migration outcomes.
Organization cleanup updates
ddpui/services/org_cleanup_service.py
Cleanup collects dataflow IDs before deletion, removes user preferences before users, and explicitly deletes additional organization-related records.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OrgTaskFunctions
  participant PipelineService
  participant PrefectService
  participant OrgDataFlowv1
  OrgTaskFunctions->>PipelineService: build prerequisite and primary task chain
  OrgTaskFunctions->>PrefectService: create deployment
  PrefectService-->>OrgTaskFunctions: return deployment data
  OrgTaskFunctions->>OrgDataFlowv1: save ordered task mappings
Loading

Possibly related PRs

Suggested reviewers: himanshudube97, siddhant3030

🚥 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 matches the main change: manual transform tasks now run through deployments and dependency tasks are excluded from the UI list.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch keep-core-manual-transform-tasks

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.

❤️ Share

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

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.86207% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.70%. Comparing base (9191252) to head (59df401).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
ddpui/services/org_cleanup_service.py 35.29% 11 Missing ⚠️
ddpui/core/orgtaskfunctions.py 80.00% 6 Missing ⚠️
ddpui/api/orgtask_api.py 92.30% 2 Missing ⚠️
ddpui/api/data_api.py 0.00% 1 Missing ⚠️
ddpui/core/orchestrate/pipeline_service.py 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1431      +/-   ##
==========================================
+ Coverage   61.67%   61.70%   +0.03%     
==========================================
  Files         152      152              
  Lines       17779    17813      +34     
==========================================
+ Hits        10965    10992      +27     
- Misses       6814     6821       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
ddpui/core/orchestrate/pipeline_service.py (1)

535-537: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Drop or wire through payload. ddpui/api/pipeline_api.py:175-189 still accepts TaskParameters and passes them into PipelineService.run_pipeline, but run_pipeline ignores them entirely. Remove the parameter from the API/service signature or forward it into the run; otherwise client-supplied flags/options are silently discarded.

🤖 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 `@ddpui/core/orchestrate/pipeline_service.py` around lines 535 - 537, Update
run_pipeline and its caller in pipeline_api.py so the TaskParameters payload is
no longer silently ignored: either remove payload from both signatures and
calls, or wire it through to the pipeline execution path where client-supplied
options are consumed. Keep the API and service signatures consistent.
🧹 Nitpick comments (4)
ddpui/services/org_cleanup_service.py (1)

323-337: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Optimize UserPreferences deletion by avoiding N+1 queries.

The current implementation iteratively queries and deletes UserPreferences for each OrgUser, which leads to an N+1 query pattern (executing two queries per user). You can optimize this by bulk-deleting the preferences for the entire organization before iterating over the users. This approach also allows you to accurately log the intended deletion count during a dry_run, which is currently skipped.

♻️ Proposed refactor
     def delete_orgusers(self):
         """
         deletes all org users; first removes UserPreferences rows that FK to
         each OrgUser (they don't CASCADE) so the OrgUser delete doesn't
         violate the FK constraint.
         """
+        n_prefs = UserPreferences.objects.filter(orguser__org=self.org).count()
+        if n_prefs:
+            logger.info("will delete %s UserPreferences row(s) for org users", n_prefs)
+            if not self.dry_run:
+                UserPreferences.objects.filter(orguser__org=self.org).delete()
+
         for orguser in OrgUser.objects.filter(org=self.org):
             logger.info("will delete orguser %s", orguser.user.email)
             if not self.dry_run:
-                n_prefs = UserPreferences.objects.filter(orguser=orguser).count()
-                if n_prefs:
-                    logger.info(
-                        "deleting %s UserPreferences row(s) attached to orguser %s",
-                        n_prefs,
-                        orguser.user.email,
-                    )
-                    UserPreferences.objects.filter(orguser=orguser).delete()
                 orguser.delete()
🤖 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 `@ddpui/services/org_cleanup_service.py` around lines 323 - 337, Update the
organization user-deletion flow to bulk-query and delete all UserPreferences
associated with self.org before iterating through OrgUser records, eliminating
the per-user count and delete queries. Preserve informative logging, including
the total intended preference count during dry_run, and keep the actual deletion
gated by the existing dry_run condition.
ddpui/ddpdbt/dbt_service.py (1)

141-150: 📐 Maintainability & Code Quality | 🔵 Trivial

Duplicate sanitized-block-name persistence logic (see consolidated comment).

Same check-then-create pattern as ddpui/api/orgtask_api.py:190-199. Correct behavior, but duplicated — see the consolidated refactor suggestion.

🤖 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 `@ddpui/ddpdbt/dbt_service.py` around lines 141 - 150, The sanitized block-name
check-and-create logic is duplicated between the dbt service and orgtask API.
Extract the shared persistence behavior into a reusable helper, then update the
relevant flow around the current OrgPrefectBlockv1.objects.filter/create calls
to use it while preserving the org, SECRET block type, and stored_block_name
values.
ddpui/tests/api_tests/test_orgtask_api.py (1)

193-202: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fixture doesn't build the actual dependency chain it claims to mirror.

Each manual deployment here maps only the primary task; production chains always prepend git-pull/git-clone + dbt-clean + dbt-deps (see create_prefect_deployment_for_dbtcore_task). This is enough for the current slug-based assertions, but it means the new cross-chain lock-aggregation logic in get_prefect_transformation_tasks (locks on shared prep tasks surfacing across sibling dataflows) is never actually exercised by these tests.

♻️ Suggested fix: seed the full chain per deployment
-        if task.slug in ("dbt-run", "dbt-test", "dbt-seed"):
-            new_dataflow = OrgDataFlowv1.objects.create(
-                org=org,
-                name=f"test-{task.slug}-deployment",
-                deployment_name=f"test-{task.slug}-deployment",
-                deployment_id=f"test-{task.slug}-deployment-id",
-                dataflow_type="manual",
-            )
-
-            DataflowOrgTask.objects.create(
-                dataflow=new_dataflow,
-                orgtask=org_task,
-            )
+        if task.slug in ("dbt-run", "dbt-test", "dbt-seed"):
+            new_dataflow = OrgDataFlowv1.objects.create(
+                org=org,
+                name=f"test-{task.slug}-deployment",
+                deployment_name=f"test-{task.slug}-deployment",
+                deployment_id=f"test-{task.slug}-deployment-id",
+                dataflow_type="manual",
+            )
+            chain_tasks = [
+                OrgTask.objects.get(org=org, task__slug="git-pull"),
+                OrgTask.objects.get(org=org, task__slug="dbt-clean"),
+                OrgTask.objects.get(org=org, task__slug="dbt-deps"),
+                org_task,
+            ]
+            for idx, chained in enumerate(chain_tasks):
+                DataflowOrgTask.objects.create(dataflow=new_dataflow, orgtask=chained, seq=idx)
🤖 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 `@ddpui/tests/api_tests/test_orgtask_api.py` around lines 193 - 202, Expand the
manual deployment fixture in the task loop to seed the complete dbt dependency
chain, not only the primary task: include the production-equivalent
git-pull/git-clone, dbt-clean, and dbt-deps tasks linked to each deployment
before the LONG_RUNNING dbt task. Reuse the chain construction behavior from
create_prefect_deployment_for_dbtcore_task so get_prefect_transformation_tasks
exercises shared preparation-task lock aggregation across sibling dataflows.
ddpui/api/orgtask_api.py (1)

190-199: 📐 Maintainability & Code Quality | 🔵 Trivial

Duplicate sanitized-block-name persistence logic.

Correct fix (using the proxy-returned block_name for the exists-check/create, since OrgPrefectBlockv1.block_name is unique), but the identical check-then-create block is copy-pasted in ddpui/ddpdbt/dbt_service.py (update_github_pat_storage). Worth extracting into a shared helper (see consolidated note).

🤖 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 `@ddpui/api/orgtask_api.py` around lines 190 - 199, The sanitized block-name
persistence check is duplicated between the current flow and
dbt_service.update_github_pat_storage. Extract the shared OrgPrefectBlockv1
exists-check/create behavior into a reusable helper, then update both callers to
use it with the proxy-returned block_name while preserving the org and SECRET
values.
🤖 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 `@ddpui/core/orgtaskfunctions.py`:
- Around line 239-244: Wrap the entire DataflowOrgTask creation loop in the
surrounding function with an atomic database transaction so all mappings are
committed together or rolled back on any failure. Update the function containing
the mapped_orgtasks loop, preserving the existing dataflow, orgtask, and seq
values and ensuring no partial rows remain if creation fails.

In `@ddpui/management/commands/backfill_manual_transform_tasks_dependencies.py`:
- Around line 216-219: Make the DataflowOrgTask delete-and-recreate sequence in
the backfill command atomic by wrapping the mapping update around the existing
dataflow update flow in a Django transaction. Ensure any failure in delete or
creation rolls back all mapping changes, while retaining the existing chain
ordering and only committing after the Prefect update and local mapping
recreation succeed.
- Around line 154-202: Move the dry_run guard in the manual dataflow update loop
so it runs before the get_or_create calls for git clone/pull, dbt clean, and dbt
deps OrgTasks. In dry-run mode, report the planned chained-task update without
invoking PipelineService methods that can write to the database; preserve the
existing non-dry-run chain construction and update behavior.

---

Outside diff comments:
In `@ddpui/core/orchestrate/pipeline_service.py`:
- Around line 535-537: Update run_pipeline and its caller in pipeline_api.py so
the TaskParameters payload is no longer silently ignored: either remove payload
from both signatures and calls, or wire it through to the pipeline execution
path where client-supplied options are consumed. Keep the API and service
signatures consistent.

---

Nitpick comments:
In `@ddpui/api/orgtask_api.py`:
- Around line 190-199: The sanitized block-name persistence check is duplicated
between the current flow and dbt_service.update_github_pat_storage. Extract the
shared OrgPrefectBlockv1 exists-check/create behavior into a reusable helper,
then update both callers to use it with the proxy-returned block_name while
preserving the org and SECRET values.

In `@ddpui/ddpdbt/dbt_service.py`:
- Around line 141-150: The sanitized block-name check-and-create logic is
duplicated between the dbt service and orgtask API. Extract the shared
persistence behavior into a reusable helper, then update the relevant flow
around the current OrgPrefectBlockv1.objects.filter/create calls to use it while
preserving the org, SECRET block type, and stored_block_name values.

In `@ddpui/services/org_cleanup_service.py`:
- Around line 323-337: Update the organization user-deletion flow to bulk-query
and delete all UserPreferences associated with self.org before iterating through
OrgUser records, eliminating the per-user count and delete queries. Preserve
informative logging, including the total intended preference count during
dry_run, and keep the actual deletion gated by the existing dry_run condition.

In `@ddpui/tests/api_tests/test_orgtask_api.py`:
- Around line 193-202: Expand the manual deployment fixture in the task loop to
seed the complete dbt dependency chain, not only the primary task: include the
production-equivalent git-pull/git-clone, dbt-clean, and dbt-deps tasks linked
to each deployment before the LONG_RUNNING dbt task. Reuse the chain
construction behavior from create_prefect_deployment_for_dbtcore_task so
get_prefect_transformation_tasks exercises shared preparation-task lock
aggregation across sibling dataflows.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc78abd-7740-4dfd-870d-5d5c08992f8d

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6824c and 7c2d658.

📒 Files selected for processing (9)
  • ddpui/api/data_api.py
  • ddpui/api/orgtask_api.py
  • ddpui/core/orchestrate/pipeline_service.py
  • ddpui/core/orgtaskfunctions.py
  • ddpui/ddpdbt/dbt_service.py
  • ddpui/management/commands/backfill_manual_transform_tasks_dependencies.py
  • ddpui/services/org_cleanup_service.py
  • ddpui/tests/api_tests/test_orgtask_api.py
  • ddpui/utils/constants.py

Comment on lines +239 to +244
for idx, chained_orgtask in enumerate(mapped_orgtasks):
DataflowOrgTask.objects.create(
dataflow=new_dataflow,
orgtask=chained_orgtask,
seq=idx,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wrap the multi-row DataflowOrgTask creation in a transaction.

This loop now creates up to 4 rows (was a single .create() before). The Prefect deployment has already been created/pointed at the full chained config by this point; if the loop fails partway (e.g. on the 2nd or 3rd item), the dataflow ends up with an incomplete/incorrect set of mappings while Prefect already expects the full chain — run_pipeline would then compute a wrong org_tasks ordering (or fail with "no org task mapped") for that deployment. This same pattern is duplicated in the backfill command (see consolidated comment).

🛡️ Proposed fix
+from django.db import transaction
...
-    for idx, chained_orgtask in enumerate(mapped_orgtasks):
-        DataflowOrgTask.objects.create(
-            dataflow=new_dataflow,
-            orgtask=chained_orgtask,
-            seq=idx,
-        )
+    with transaction.atomic():
+        for idx, chained_orgtask in enumerate(mapped_orgtasks):
+            DataflowOrgTask.objects.create(
+                dataflow=new_dataflow,
+                orgtask=chained_orgtask,
+                seq=idx,
+            )
📝 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.

Suggested change
for idx, chained_orgtask in enumerate(mapped_orgtasks):
DataflowOrgTask.objects.create(
dataflow=new_dataflow,
orgtask=chained_orgtask,
seq=idx,
)
with transaction.atomic():
for idx, chained_orgtask in enumerate(mapped_orgtasks):
DataflowOrgTask.objects.create(
dataflow=new_dataflow,
orgtask=chained_orgtask,
seq=idx,
)
🤖 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 `@ddpui/core/orgtaskfunctions.py` around lines 239 - 244, Wrap the entire
DataflowOrgTask creation loop in the surrounding function with an atomic
database transaction so all mappings are committed together or rolled back on
any failure. Update the function containing the mapped_orgtasks loop, preserving
the existing dataflow, orgtask, and seq values and ensuring no partial rows
remain if creation fails.

Comment on lines +154 to +202
for dataflow in manual_dataflows:
# primary = last-by-seq mapped orgtask that is a LONG_RUNNING dbt task
primary = None
for dfot in sorted(
dataflow.datafloworgtasks.all(),
key=lambda d: d.seq,
reverse=True,
):
ot = dfot.orgtask
if ot.task.type == TaskType.DBT and ot.task.slug in LONG_RUNNING_TASKS:
primary = ot
break
if primary is None:
# dbt-cloud-job or orphan — not migratable through this path
continue

# build the desired chain
if is_eks:
git_orgtask = PipelineService.get_or_create_git_clone_orgtask(org)
else:
git_orgtask = PipelineService.get_or_create_git_pull_orgtask(org)

chain = [
git_orgtask,
PipelineService.get_or_create_dbt_clean_orgtask(org),
PipelineService.get_or_create_dbt_deps_orgtask(org),
primary,
]

new_task_configs, err = pipeline_with_orgtasks(
org,
chain,
cli_block=cli_profile_block,
dbt_project_params=dbt_project_params,
gitrepo_url=org.dbt.gitrepo_url,
)
if err:
self.stderr.write(f" [update] {primary.task.slug}: build failed: {err}")
continue

new_deployment_params = {"config": {"tasks": new_task_configs, "org_slug": org.slug}}

if dry_run:
self.stdout.write(
f" [DRY] [update] {primary.task.slug} ({dataflow.deployment_id}): "
f"would set {len(new_task_configs)} chained tasks and rewrite mappings"
)
result["updated"] += 1
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

--dry-run still writes to the DB.

PipelineService.get_or_create_git_clone_orgtask / get_or_create_git_pull_orgtask / get_or_create_dbt_clean_orgtask / get_or_create_dbt_deps_orgtask are called unconditionally here, before the if dry_run: check further down (line ~196). These are real get_or_create writes — if the org doesn't already have these prep OrgTask rows, running with --dry-run will create them, contradicting the documented contract ("--dry-run: Print what would happen without touching Prefect or DB") and the help text. Step B (below) and the top-of-function prep block both correctly gate their writes behind the dry-run flag; this block doesn't.

🛡️ Proposed fix — check dry_run before any get_or_create call
             if primary is None:
                 # dbt-cloud-job or orphan — not migratable through this path
                 continue

+            if dry_run:
+                self.stdout.write(
+                    f"  [DRY] [update] {primary.task.slug} ({dataflow.deployment_id}): "
+                    f"would rewrite chain (git + dbt-clean + dbt-deps + primary) and mappings"
+                )
+                result["updated"] += 1
+                continue
+
             # build the desired chain
             if is_eks:
                 git_orgtask = PipelineService.get_or_create_git_clone_orgtask(org)
             else:
                 git_orgtask = PipelineService.get_or_create_git_pull_orgtask(org)

             chain = [
                 git_orgtask,
                 PipelineService.get_or_create_dbt_clean_orgtask(org),
                 PipelineService.get_or_create_dbt_deps_orgtask(org),
                 primary,
             ]

             new_task_configs, err = pipeline_with_orgtasks(...)
             if err:
                 self.stderr.write(f"  [update] {primary.task.slug}: build failed: {err}")
                 continue

             new_deployment_params = {"config": {"tasks": new_task_configs, "org_slug": org.slug}}

-            if dry_run:
-                self.stdout.write(
-                    f"  [DRY] [update] {primary.task.slug} ({dataflow.deployment_id}): "
-                    f"would set {len(new_task_configs)} chained tasks and rewrite mappings"
-                )
-                result["updated"] += 1
-                continue
-
             try:
                 prefect_service.update_dataflow_v1(...)
📝 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.

Suggested change
for dataflow in manual_dataflows:
# primary = last-by-seq mapped orgtask that is a LONG_RUNNING dbt task
primary = None
for dfot in sorted(
dataflow.datafloworgtasks.all(),
key=lambda d: d.seq,
reverse=True,
):
ot = dfot.orgtask
if ot.task.type == TaskType.DBT and ot.task.slug in LONG_RUNNING_TASKS:
primary = ot
break
if primary is None:
# dbt-cloud-job or orphan — not migratable through this path
continue
# build the desired chain
if is_eks:
git_orgtask = PipelineService.get_or_create_git_clone_orgtask(org)
else:
git_orgtask = PipelineService.get_or_create_git_pull_orgtask(org)
chain = [
git_orgtask,
PipelineService.get_or_create_dbt_clean_orgtask(org),
PipelineService.get_or_create_dbt_deps_orgtask(org),
primary,
]
new_task_configs, err = pipeline_with_orgtasks(
org,
chain,
cli_block=cli_profile_block,
dbt_project_params=dbt_project_params,
gitrepo_url=org.dbt.gitrepo_url,
)
if err:
self.stderr.write(f" [update] {primary.task.slug}: build failed: {err}")
continue
new_deployment_params = {"config": {"tasks": new_task_configs, "org_slug": org.slug}}
if dry_run:
self.stdout.write(
f" [DRY] [update] {primary.task.slug} ({dataflow.deployment_id}): "
f"would set {len(new_task_configs)} chained tasks and rewrite mappings"
)
result["updated"] += 1
continue
for dataflow in manual_dataflows:
# primary = last-by-seq mapped orgtask that is a LONG_RUNNING dbt task
primary = None
for dfot in sorted(
dataflow.datafloworgtasks.all(),
key=lambda d: d.seq,
reverse=True,
):
ot = dfot.orgtask
if ot.task.type == TaskType.DBT and ot.task.slug in LONG_RUNNING_TASKS:
primary = ot
break
if primary is None:
# dbt-cloud-job or orphan — not migratable through this path
continue
if dry_run:
self.stdout.write(
f" [DRY] [update] {primary.task.slug} ({dataflow.deployment_id}): "
f"would rewrite chain (git + dbt-clean + dbt-deps + primary) and mappings"
)
result["updated"] += 1
continue
# build the desired chain
if is_eks:
git_orgtask = PipelineService.get_or_create_git_clone_orgtask(org)
else:
git_orgtask = PipelineService.get_or_create_git_pull_orgtask(org)
chain = [
git_orgtask,
PipelineService.get_or_create_dbt_clean_orgtask(org),
PipelineService.get_or_create_dbt_deps_orgtask(org),
primary,
]
new_task_configs, err = pipeline_with_orgtasks(
org,
chain,
cli_block=cli_profile_block,
dbt_project_params=dbt_project_params,
gitrepo_url=org.dbt.gitrepo_url,
)
if err:
self.stderr.write(f" [update] {primary.task.slug}: build failed: {err}")
continue
new_deployment_params = {"config": {"tasks": new_task_configs, "org_slug": org.slug}}
🤖 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 `@ddpui/management/commands/backfill_manual_transform_tasks_dependencies.py`
around lines 154 - 202, Move the dry_run guard in the manual dataflow update
loop so it runs before the get_or_create calls for git clone/pull, dbt clean,
and dbt deps OrgTasks. In dry-run mode, report the planned chained-task update
without invoking PipelineService methods that can write to the database;
preserve the existing non-dry-run chain construction and update behavior.

Comment on lines +216 to +219
# wipe + recreate DataflowOrgTask mappings so seq matches chain order
DataflowOrgTask.objects.filter(dataflow=dataflow).delete()
for idx, orgtask in enumerate(chain):
DataflowOrgTask.objects.create(dataflow=dataflow, orgtask=orgtask, seq=idx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wipe+recreate of DataflowOrgTask mappings isn't atomic.

This happens after prefect_service.update_dataflow_v1 already succeeded. If the delete or one of the recreate calls fails partway through, the deployment on Prefect now expects the new chained config while Django's local mapping table is left incomplete — see consolidated comment (shared root cause with ddpui/core/orgtaskfunctions.py).

🤖 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 `@ddpui/management/commands/backfill_manual_transform_tasks_dependencies.py`
around lines 216 - 219, Make the DataflowOrgTask delete-and-recreate sequence in
the backfill command atomic by wrapping the mapping update around the existing
dataflow update flow in a Django transaction. Ensure any failure in delete or
creation rolls back all mapping changes, while retaining the existing chain
ordering and only committing after the Prefect update and local mapping
recreation succeed.

@himanshudube97
himanshudube97 merged commit 409714a into main Jul 14, 2026
4 of 5 checks passed
@himanshudube97
himanshudube97 deleted the keep-core-manual-transform-tasks branch July 14, 2026 15:06
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