feat: Quick Deploy button in model tab with scope selection and Run History integration#62
Conversation
|
| Filename | Overview |
|---|---|
| backend/backend/core/scheduler/views.py | Adds _dispatch_task_run helper + three new endpoints; non-dict truthy cfg values are not guarded in list_deploy_candidates and trigger_task_once_for_model (AttributeError risk) |
| backend/backend/core/scheduler/celery_tasks.py | Adds models_override/trigger kwargs with safe defaults; retry path correctly propagates both; scope is recomputed at entry so no extra kwarg needed |
| backend/backend/core/scheduler/urls.py | Additive URL registration for three new endpoints; correct path patterns and names |
| frontend/src/ide/editor/no-code-model/no-code-model.jsx | Adds Quick Deploy modal flow, recent-runs popup, log-level filter, and socket cleanup fix; dedup guard in fetchRecentRuns allows duplicate in-flight requests |
| frontend/src/ide/run-history/Runhistory.jsx | Replaces Source column with Trigger + Scope, adds two composable filters, moves getRunTriggerScope to module scope; auto-expand correctly keyed to backUpData |
| frontend/src/ide/run-history/RunHistory.css | CSS overrides to visually bind expanded error rows to their parent; clean and non-breaking |
| frontend/src/ide/scheduler/JobListTable.jsx | Tooltip text updated from 'run' to 'Deploy Job'; cosmetic-only change |
| frontend/src/ide/scheduler/service.js | Three new service methods (runTaskForModel, listDeployCandidates, listRecentRunsForModel) with correct encodeURIComponent usage |
Sequence Diagram
sequenceDiagram
participant U as User (model tab)
participant FE as no-code-model.jsx
participant SVC as service.js
participant BE as views.py
participant CL as Celery
participant DB as TaskRunHistory
U->>FE: Click Quick Deploy
FE->>SVC: listDeployCandidates(projectId, modelName)
SVC->>BE: GET /jobs/quick-deploy/candidates/{model}
BE-->>FE: candidates[]
alt 0 candidates
FE-->>U: No Deployment Job Found modal
else 1 candidate
FE-->>U: confirm modal (job + env)
else 2+ candidates
FE-->>U: job picker radio + confirm modal
end
U->>FE: Select scope + tick checkbox + Run
alt scope = model
FE->>SVC: runTaskForModel(projectId, taskId, modelName)
SVC->>BE: POST /jobs/trigger-periodic-task/{id}/model/{model}
else scope = job
FE->>SVC: runTask(projectId, taskId)
SVC->>BE: POST /jobs/trigger-periodic-task/{id}
end
BE->>BE: _dispatch_task_run(task, userId, models_override?)
BE->>CL: apply_async(trigger=manual, models_override?)
CL->>DB: TaskRunHistory.create(trigger, scope, models_override)
BE-->>FE: success
FE-->>U: toast + refresh
U->>FE: Click chevron dropdown
FE->>SVC: listRecentRunsForModel(projectId, modelName, 5)
SVC->>BE: GET /jobs/quick-deploy/recent-runs/{model}
BE-->>FE: runs[] with trigger/scope back-compat
FE-->>U: Recent runs popup
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/backend/core/scheduler/views.py
Line: 775-776
Comment:
**Non-dict truthy config causes `AttributeError`**
`not cfg` short-circuits for `None` and `{}`, but for any other truthy non-dict value (e.g. a JSON boolean `true` stored as `model_configs["my_model"] = True`), `not cfg` is `False` and `cfg.get("enabled", True)` raises `AttributeError`. The previous null-safety fix already established the right pattern — `isinstance(m_cfg, dict)` is used in `enabled_model_count` on line 780 but not here.
The same gap exists in `trigger_task_once_for_model` (line 696).
```suggestion
if not isinstance(cfg, dict) or not cfg.get("enabled", True):
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: backend/backend/core/scheduler/views.py
Line: 695-696
Comment:
**Non-dict truthy config causes `AttributeError`**
Same issue as in `list_deploy_candidates` line 776: `not model_cfg` short-circuits for `None`/`{}` but not for other truthy non-dict values. If `model_cfg` is, say, a JSON boolean `True`, `not model_cfg` is `False` and `model_cfg.get("enabled", True)` raises `AttributeError`. The `enabled_model_count` sibling check already uses `isinstance(m_cfg, dict)` as the guard.
```suggestion
if not isinstance(model_cfg, dict) or not model_cfg.get("enabled", True):
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: frontend/src/ide/editor/no-code-model/no-code-model.jsx
Line: 1678
Comment:
**Dedup guard doesn't cover in-flight fetches**
The early-return only fires when `!loading`. If the user opens the chevron dropdown while a fetch is already in progress (`loading === true`), the condition evaluates to `false` and a second concurrent request is dispatched. The two responses will race to set state; the final value is correct but the extra request is wasteful.
```suggestion
if (recentRunsState.loading || recentRunsState.fetchedFor === name) return;
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (4): Last reviewed commit: "fix: address reviewer feedback — null co..." | Re-trigger Greptile
|
Changes requested (small, mostly polish):
|
There was a problem hiding this comment.
@abhizipstack One small leftover (non-blocking, can ship): the inline boxShadow: inset 3px 0 0 0 ${token.colorError} on onRow is still applied to any expanded row regardless of status. Auto-expand is now FAILURE-only so it's fine in practice, but if a user manually expands a SUCCESS/RETRY row they'll get a misleading red bar. Worth a follow-up to gate on record.status === "FAILURE" or color-match by status.
Quick Deploy runs the current model through the same pipeline as the
scheduler's "Run Now" — reusing an existing enabled job's environment,
model_configs (materialization, incremental, watermark), retries, Slack
notifications, and TaskRunHistory — but scopes the DAG run to just the
current model.
Backend
- trigger_scheduled_run accepts an optional models_override list. When
provided it's passed to execute_visitran_run_command as current_models
so only those models execute. Recorded in TaskRunHistory.kwargs with
source="quick_deploy" so ad-hoc runs are distinguishable from
scheduled ones. Models_override is threaded through the retry path.
- New endpoint POST /jobs/trigger-periodic-task/<task>/model/<model>
dispatches a single-model run; validates the model is present and
enabled on the job before running.
- New endpoint GET /jobs/quick-deploy/candidates/<model> returns the
jobs in the project that include the model with enabled=true.
- trigger_task_once and the new single-model variant share a
_dispatch_task_run helper (Celery-first with sync fallback).
Frontend
- Primary "Quick Deploy" button in the model tab action row.
- Click → fetches candidates:
- 0 → modal explains no job covers this model and links to the
scheduler page to create one.
- 1 → confirm modal shows job + environment before running.
- 2+ → picker modal with a radio list of qualifying jobs.
- On success: success notification with job + environment summary,
triggers explorer refresh so the status badge reflects the new run.
- On failure: existing error notification path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Run History now shows a Source column with a blue "Quick Deploy" tag
and the overridden model name(s) for ad-hoc runs; scheduled runs show
a muted "Scheduled" label. Reads the markers already written into
TaskRunHistory.kwargs ("source" + "models_override").
- list_deploy_candidates was returning environment_name="" because it
read task.environment.name; the field on EnvironmentModels is
environment_name. Fixed to prefer environment_name with a fallback
to name for defensive cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend the Quick Deploy control into a Space.Compact pair: the primary button still opens the deploy flow; the adjacent chevron opens a small popup listing recent runs for the current model, mixing scheduled and quick-deploy runs. A "View full Run History" link-button at the bottom navigates to /project/job/history. Backend - GET /jobs/quick-deploy/recent-runs/<model>?limit=5 returns the most recent TaskRunHistory entries where the job's model_configs includes the model. Each row exposes status, start_time, environment_name, task_name, error_message, and source (derived from kwargs.source, defaulting to "scheduled"). Frontend - useJobService gets listRecentRunsForModel(projId, modelName, limit). - Dropdown fetches on open (once per model name — cached within component state). Panel shows colored status tag, a Quick Deploy / Scheduled source tag, job name, environment, and a relative time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hardcoded #fff / #555 / #888 / #f0f0f0 rendered illegibly on dark themes. Swap to antd theme tokens so the panel follows the active theme: colorBgElevated for the backdrop, colorTextSecondary for muted headers/empty states, colorBorderSecondary for the row separators, boxShadowSecondary + borderRadiusLG for the elevated card feel. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run History now has a third filter next to Job and Status that narrows
rows by kwargs.source ("quick_deploy" vs scheduled, where scheduled is
defined as everything lacking the quick_deploy marker). The filter is
client-side on the currently loaded page; it plays with the Status
filter (both apply). Switching jobs resets the Source filter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original "Source" marker conflated two orthogonal dimensions:
whether a run was triggered manually or by cron, and whether it ran the
full job or a single model. Manual job triggers ("Run Now") were
indistinguishable from cron runs because both lacked the
source="quick_deploy" marker, which was wrong.
Backend
- trigger_scheduled_run gains trigger: str = "scheduled" and derives
scope ("model" if models_override else "job"). Both are recorded on
TaskRunHistory.kwargs on every run (including retries).
- _dispatch_task_run (used by both trigger_task_once and the new
single-model variant) always sends trigger="manual"; the Celery beat
path keeps the "scheduled" default, so manual job deploys are now
correctly distinguished from cron-driven runs.
- list_recent_runs_for_model returns trigger + scope, with back-compat
fallback for legacy rows that only carried kwargs.source.
- kwargs.source is no longer written.
Frontend
- Run History: replaced the Source column with Trigger (Manual /
Scheduled) and Scope (Full job / Single model, with the targeted
model names). The Source filter is now two filters — Trigger and
Scope — each composable with Status and with each other. A shared
helper derives trigger/scope with legacy-row back-compat.
- Recent-runs popup on the model tab shows both tags.
- Button labels updated to match the new taxonomy: model tab's
"Quick Deploy" → "Deploy Model"; jobs list tooltip "run" →
"Deploy Job". Success toast and modal heading updated accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keeps "Quick Deploy" as the toolbar button but lets users pick scope at
the point of confirmation, directly in the model tab — so they no
longer need to bounce to the Jobs List to run the full pipeline that
includes the model they are editing.
- Modal's confirm step now shows two side-by-side cards:
▸ "Run this model only" — uses runTaskForModel (existing)
▸ "Run full job" — uses runTask (existing); shows the enabled
model count so users see the blast radius before committing.
- If multiple candidate jobs include the model, the job picker radio
group appears above the scope cards so the whole decision happens in
one modal. Single-candidate case skips the picker.
- Cards act as the primary CTAs; the submit state highlights the
clicked card's border and dims the other while the dispatch is in
flight. Footer keeps just a Cancel button.
- Success toast copy differentiates model vs job deploys.
Backend candidate response now includes enabled_model_count derived
from the job's model_configs so the "Run full job" card can show
"Runs all N enabled models".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the auto-triggering scope cards with a single checkbox
("Also run the other models in this job") plus a primary "Run model" /
"Run full job" button in the footer. Picking a job in the radio no
longer fires a deploy — the user now has one clear moment where they
commit, and the button label reflects the current scope so they see
exactly what will run.
- Scope lives on quickDeployModal.runFullJob; starts unchecked
(model-only) every time the modal opens, and resets when the user
switches the selected job so scope can't silently carry over.
- Checkbox description swaps text based on state so the blast radius
is visible before clicking.
- Footer button label: "Run model" → "Run full job" when checkbox is
ticked, for an unambiguous preview of the action.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final modal flow, matches the agreed UX:
1. (Multi-candidate) radio picker for the job — selection only,
doesn't trigger anything.
2. Two scope cards — "Run this model only" / "Run full job" — act
as visual radios; clicking selects the scope, does not dispatch.
3. Confirmation checkbox ("I understand this will run <scope> against
environment X") — gated on a scope being picked.
4. Primary footer button "Run model" / "Run full job" — disabled
until a scope is selected AND the confirmation checkbox is ticked.
Scope is stored in quickDeployModal.selectedScope and resets when the
user flips the candidate radio so stale scope can't carry to a
different job. The button is the only thing that dispatches; clicking
a card never triggers a run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expanded error rows previously rendered as a flat danger-red text
block with no visual connection to the failed run above — users
couldn't immediately tell the error belonged to that row.
- Wrap the expanded panel in an error-themed container: a 3px
colorError left border, colorErrorBg background, and a small header
("Error from this run · <start_time>") with a CloseCircleFilled
icon so the panel clearly extends the row that spawned it.
- Error text itself goes inside an antd Alert (showIcon=false) using a
<pre> with max-height + scroll so long stack traces don't blow out
the page; whitespace and line breaks are preserved (previously
Typography collapsed them).
- All colors go through theme tokens so light/dark both work.
- Dropped the now-redundant .runhistory-error-row padding rule.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expanded error rows were rendering as a full-width sibling row, separated from the failed run above by antd's default row border. Users couldn't tell at a glance which run the error belonged to. - rowClassName marks expanded parent rows; their bottom border is now transparent so visual flow continues down into the expanded panel. - The parent row gets an inset 3px red box-shadow on its left edge (via onRow, theme-token-driven) that lines up with the panel's own red left border — the red stripe now runs top-to-bottom through both parts, reading as one grouped item. - .ant-table-expanded-row <td> is neutralized (no top border, no padding, transparent background incl. hover) so only the inner styled panel shows; no antd chrome separates parent from child. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Failed runs were auto-expanded whenever the data loaded, which made error panels land in the user's face every visit. Reset expandedRowKeys on data change so rows start collapsed; users click the expander to see the stack trace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: list_deploy_candidates crashed with AttributeError when a task had
no environment. Guard with `if env else ""` (same pattern used in
list_recent_runs_for_model).
P2: list_deploy_candidates loaded all project tasks and filtered in
Python. Added model_configs__has_key=model_name to the queryset so
the DB does the heavy lifting.
P2: Recent-runs popup showed stale data after a Quick Deploy because
fetchedFor wasn't cleared. Now reset to null after successful
dispatch so the next dropdown open refetches.
P2: getRunTriggerScope was defined inside the Runhistory component,
triggering react-hooks/exhaustive-deps warnings and unnecessary
re-creation. Moved to module scope — it has no dependency on
component state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: enabled_model_count crashed with AttributeError when a model in
model_configs had a null/non-dict value. Added isinstance(m_cfg,
dict) guard before calling .get().
P2: FAILURE rows no longer auto-expanded after the earlier "collapse
by default" change. Restored auto-expand for FAILURE rows only,
keyed on backUpData (fresh loads) so filter changes don't reset
user-expanded rows.
P2: Replaced the local formatRelativeTime in no-code-model.jsx with
the shared getRelativeTime from common/helpers.js which already
handles both past and future dates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
de97852 to
289eec0
Compare
What
Model Tab — Quick Deploy
Run History — Trigger + Scope taxonomy
kwargs.source === "quick_deploy"render correctly as Manual + Single model.Jobs List
Backend
trigger_scheduled_rungainsmodels_override: listandtrigger: strkwargs. Whenmodels_overrideis set, scopes the DAG run to those models viaexecute_visitran_run_command(current_models=...). Recordstrigger("manual" / "scheduled") andscope("model" / "job") onTaskRunHistory.kwargsfor every run (including retries).trigger_task_oncerefactored into shared_dispatch_task_runhelper (Celery-first with sync fallback) used by both the existing full-job trigger and the new single-model trigger.POST /jobs/trigger-periodic-task/<task_id>/model/<model_name>— validates the model is enabled on the job, dispatches a scoped run.GET /jobs/quick-deploy/candidates/<model_name>— returns jobs in the project that include the model withenabled_model_count.GET /jobs/quick-deploy/recent-runs/<model_name>?limit=5— returns recent runs for any job that includes this model, with trigger/scope/models_override and back-compat.Why
Users had no way to deploy a model from the model tab — they had to navigate to the Jobs List and trigger the whole job. This feature lets users deploy the current model (or the full job) with a clear scope-selection and confirmation flow, directly from where they're editing.
The Run History taxonomy was conflating trigger type (manual vs scheduled) with scope (model vs job), making it impossible to filter "show me only the runs I manually kicked off" or "only single-model deploys".
How
useJobServicehooks forlistDeployCandidates,runTaskForModel,runTask,listRecentRunsForModel. Modal state machine withstep(loading/empty/confirm/pick),selectedScope(model/job), andconfirmed(checkbox).Space.CompactwithDropdownfor the recent-runs popup. All using antd theme tokens.getRunTriggerScopehelper with back-compat for legacykwargs.source. CSS.runhistory-row-expanded+.ant-table-expanded-rowoverrides to bind parent-child rows visually.onRowinjects red box-shadow on expanded parents.trigger_scheduled_runcomputesscopefrommodels_override, passestriggerthrough toTaskRunHistory.kwargsand the retry path._dispatch_task_runcentralizes Celery dispatch + sync fallback;list_deploy_candidatesfiltersmodel_configsby model name;list_recent_runs_for_modelqueries across all jobs containing the model.Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
Low risk:
trigger_task_onceis refactored to use_dispatch_task_runbut its external behavior is identical (same request/response shape).trigger_scheduled_rungains optional kwargs with defaults that preserve existing Celery beat behavior (trigger="scheduled", models_override=None).TaskRunHistory.kwargsnow includestriggerandscopeon every new run. Old rows lack these; the frontend falls back to legacykwargs.sourcehandling or defaults ("scheduled" / "job"). No migration needed.Database Migrations
None.
trigger,scope, andmodels_overridelive in the existingTaskRunHistory.kwargsJSONField.Env Config
None.
Relevant Docs
None.
Related Issues or PRs
Runhistory.jsx)Dependencies Versions
No changes.
Notes on Testing
Tested locally (host-mode: Django runserver + Celery worker/beat + React dev server, Postgres + Redis via Docker):
setRefreshModelscalled.runTask.kwargs.source="quick_deploy"rows rendered as Manual + Single model.Screenshots
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code