feat: Deploy & Run History follow-ups — polling, metrics, deep-links, DB columns, server-side filters#64
Conversation
The Quick Deploy success toast now includes a clickable "View in Run History →" link that navigates to /project/job/history?task=<id>, preselecting the job. On arrival, the Run History page auto-expands the most recent run (first row) in addition to any FAILURE rows, so the user immediately sees the deploy they just triggered. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no job covers the current model, clicking "Go to Scheduler" now navigates to /project/job/list?create=1&project=<pid>&model=<name>. The Jobs List reads these params: auto-opens the create drawer, and JobDeploy pre-enables the specified model in Model Configuration with the config panel auto-expanded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously stored only in kwargs JSON, making server-side filtering impossible. Now first-class nullable CharField columns with DB indexes, written by trigger_scheduled_run alongside kwargs. - Migration 0002 adds trigger (scheduled/manual) and scope (job/model) columns with defaults matching existing behavior. - celery_tasks.py writes both the columns and kwargs (backward compat). - Frontend getRunTriggerScope prefers top-level row.trigger / row.scope (from serializer) and falls back to kwargs for pre-migration rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces client-side filtering with server-side query params on the task_run_history endpoint. Filter changes now trigger a fresh API call with ?trigger=manual&scope=model&status=FAILURE, so results are accurate across all pages (previously client-side filtering only worked on the visible page). Backend accepts optional trigger, scope, status query params and applies them as Django ORM filters against the new DB columns from the previous migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After dispatching a deploy, the Quick Deploy button flips to "Deploying…" with a spinner and polls the latest run status every 5s. On terminal state (SUCCESS/FAILURE/REVOKED): - Clears the polling interval - Shows a completion toast with status + deep-link to Run History - Refreshes the explorer (status badges) and recent-runs cache Polling auto-cleans on component unmount. The button returns to its normal state when the run finishes or the component unmounts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After DAG execution (success or failure), trigger_scheduled_run now serializes BASE_RESULT into run.result as JSON with per-model status/end_status and aggregate passed/failed counts. Frontend insights panel renders a metrics bar when result is present: "N models attempted · X passed · Y failed" plus per-model breakdown. Falls back gracefully to scope/models display for older runs without result data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The notify service defaults to renderMarkdown: true, which wraps description in ReactMarkdown. When description is JSX (our <a> link), ReactMarkdown stringifies it via JSON.stringify, rendering as raw text instead of a clickable link. Added renderMarkdown: false to both the dispatch toast and the polling-completion toast. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Old runs have result as {} or with total=0. Guard with
record.result?.total > 0 so the metrics bar only renders when
there's actual execution data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BASE_RESULT.node_name stores str(cls) which renders as <class 'project.models.mdoela.Mdoela'>. Extract the module name (second-to-last dotted segment) so metrics show "mdoela" instead of the full class repr. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A model file can define multiple classes (e.g. SourceMdoela + Mdoela) in the same module. Using [-2] (module name) made them indistinguishable. Switch to [-1] (class name) so the metrics display shows "SourceMdoela (OK), Mdoela (OK)" instead of "mdoela (OK), mdoela (OK)". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No-code models generate a *Source class (e.g. MdoelaSource) for DAG dependency resolution alongside the user's actual model class. Both execute as DAG nodes and appear in BASE_RESULT, but users only care about their own models. Filter out classes ending with "Source" from the metrics serialization so the count and per-model list reflect user-created models only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SourceMdoela, DevPaymentsSource — the sample projects use both conventions. The generated no-code models use the prefix pattern (SourceX). Changed endswith to startswith to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| backend/backend/core/scheduler/views.py | Adds server-side filtering and _dispatch_task_run helper. Contains a P1 bug: scope is set to the model name string rather than "model" for single-model quick deploys. |
| backend/backend/core/scheduler/celery_tasks.py | Adds BASE_RESULT metric capture. Snapshot-then-clear pattern is correct; failure paths call _mark_failure before _clear_base_result. |
| backend/backend/core/scheduler/migrations/0002_taskrunhistory_trigger_scope.py | Adds nullable trigger/scope columns with indexes. null=True ensures existing rows receive NULL, preserving the kwargs fallback. |
| frontend/src/ide/run-history/Runhistory.jsx | Server-side filtering with single-trigger useEffect, deepLinkConsumed ref, metrics bar. Previous race-condition issues resolved. |
| frontend/src/ide/editor/no-code-model/no-code-model.jsx | Quick Deploy polling with correct unmount cleanup; polling doesn't pin to the specific dispatched run. |
Sequence Diagram
sequenceDiagram
participant UI as no-code-model.jsx
participant API as views.py
participant Celery as celery_tasks.py
participant DB as TaskRunHistory
UI->>API: POST trigger-periodic-task/:taskId/model/:name
API->>Celery: send_task(trigger=manual, models_override=[name])
API-->>UI: {success: true}
API->>DB: fire_event(JobTriggered scope=model_name ❌)
UI->>UI: startDeployPolling(taskId)
loop every 5s
UI->>API: GET run-history/:taskId?page=1&limit=1
API-->>UI: latest run status
alt terminal
UI->>UI: clearInterval, show toast
end
end
Celery->>DB: TaskRunHistory.create(trigger=manual, scope=model ✓)
Celery->>Celery: snapshot BASE_RESULT, clear
Celery->>DB: run.save(status=SUCCESS, result={metrics})
Comments Outside Diff (1)
-
backend/backend/core/scheduler/views.py, line 654 (link)scopeset to model name string instead of"model"When
models_overridehas exactly one element (the normal Quick Deploy path viatrigger_task_once_for_model),scopeis assignedmodels_override[0]— the model name string (e.g."Mdoela") — rather than the string"model". This incorrect value is then passed tofire_event(JobTriggered(job_name=task.task_name, scope=scope))on both the Celery and synchronous execution paths, so every Quick Deploy triggers an activity event with the wrong scope.Prompt To Fix With AI
This is a comment left during a code review. Path: backend/backend/core/scheduler/views.py Line: 654 Comment: **`scope` set to model name string instead of `"model"`** When `models_override` has exactly one element (the normal Quick Deploy path via `trigger_task_once_for_model`), `scope` is assigned `models_override[0]` — the model name string (e.g. `"Mdoela"`) — rather than the string `"model"`. This incorrect value is then passed to `fire_event(JobTriggered(job_name=task.task_name, scope=scope))` on both the Celery and synchronous execution paths, so every Quick Deploy triggers an activity event with the wrong scope. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/backend/core/scheduler/views.py
Line: 654
Comment:
**`scope` set to model name string instead of `"model"`**
When `models_override` has exactly one element (the normal Quick Deploy path via `trigger_task_once_for_model`), `scope` is assigned `models_override[0]` — the model name string (e.g. `"Mdoela"`) — rather than the string `"model"`. This incorrect value is then passed to `fire_event(JobTriggered(job_name=task.task_name, scope=scope))` on both the Celery and synchronous execution paths, so every Quick Deploy triggers an activity event with the wrong scope.
```suggestion
scope = "model" if models_override else "job"
```
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: 1964-1968
Comment:
**Polling tracks latest run, not the dispatched run**
`getLatestRunStatus` fetches `page=1, limit=1` ordered by `-start_time`, so it returns the newest `TaskRunHistory` row for the job. If a scheduled run fires on the same job between dispatch and the first poll tick (5 s), or if the user triggers two quick deploys back-to-back, the interval will report the terminal state of the wrong run.
A low-effort workaround is to record a timestamp just before dispatch and skip any run whose `start_time` predates it:
```js
const dispatchedAt = Date.now();
pollingRef.current = setInterval(async () => {
const run = await getLatestRunStatus(projectId, taskId);
if (!run || new Date(run.start_time).getTime() < dispatchedAt) return;
// proceed as before
}, 5000);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (9): Last reviewed commit: "merge: resolve conflict with main — keep..." | Re-trigger Greptile
…e bugs - Pass active filters through handlePagination and handleRefresh (P1) - Snapshot-then-clear BASE_RESULT to prevent stale metrics across worker reuse (P1) - Fix handleRefresh stale closure deps (P2) - Forward project URL param from goToScheduler to JobDeploy (P2) - Prefer DB columns over kwargs for trigger/scope in list_recent_runs_for_model (P2) - Sanitize taskId with encodeURIComponent in toast deep-links (CodeQL) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
All 6 issues addressed in 3c44484:
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
A few items to consider: 1. Same def _clean_name(raw):
if "'" in raw:
return raw.split("'")[1].split(".")[-1]PR #59 already fixed this with 2. 3. Polling has no max duration / backoff — no-code-model.jsx:370. Hardcoded 5s interval = 360 backend hits per 30-min deploy. Add a max-poll-count or exponential backoff. Also no upper-bound timeout — if backend never returns terminal status, polling runs until unmount. 4. 5. 6. |
P1: _mark_failure was called after _clear_base_result(), so failure
metrics were always empty. Swapped order: capture metrics first
via _mark_failure, then clear the global.
P1: getRunHistoryList had currentPage/pageSize in useCallback deps,
causing infinite re-creation on pagination. Removed — they're
passed as explicit arguments, not captured from closure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tahierhussain
left a comment
There was a problem hiding this comment.
@abhizipstack Please add screenshots for the UI changes addressed in this PR.
envInfo.id updates only after getRunHistoryList completes, creating a race window where pagination could fetch data for the previously selected job if the user switches jobs and changes page before the new data arrives. filterQueries.job updates immediately on job selection, so pagination always targets the correct job. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…story - Remove getRunHistoryList and pageSize from filter effect deps to prevent double-fetch when handleJobChange sets filterQuery.job - Let the filter effect be the sole fetch trigger for job/filter changes - Use a ref to track deep-link consumption so auto-expand only fires once on initial arrival, not on every data refresh Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lback Existing rows get NULL instead of "scheduled"/"job" defaults, allowing the kwargs-based fallback to correctly identify manual/model-scope runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nfigs The async getProjectModels callback was clearing modelConfigs after the prefillModel effect had already set the pre-checked model. Now preserves the prefilled model during reset. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
What
Implements 6 of 7 items from the deferred follow-ups ticket, plus bug fixes found during testing.
Deep-link toast → Run History (#5)
renderMarkdown: falseso JSX renders correctly).Pre-fill create-job from 0-candidates (#6)
/project/job/list?create=1&project=<pid>&model=<name>.First-class trigger + scope DB columns (#4)
trigger(scheduled/manual) andscope(job/model) as realCharFieldcolumns onTaskRunHistorywith DB indexes.0002_taskrunhistory_trigger_scope.trigger_scheduled_runwrites both columns and kwargs (backward compat).getRunTriggerScopeprefers top-level fields, falls back to kwargs for pre-migration rows.Server-side Run History filtering (#7)
task_run_historyendpoint accepts?trigger=,?scope=,?status=query params.Live deploy progress polling (#3)
getLatestRunStatus.Runtime metrics in Run History (#1)
trigger_scheduled_runserializesBASE_RESULTintoTaskRunHistory.resultas JSON.SourceMdoela) filtered out — only user-created models appear.result.total > 0.Deferred to separate tickets:
Why
These follow-ups close gaps identified during the Quick Deploy and Run History work: no way to navigate from a toast to the specific run, no way to create a job from the 0-candidates flow, filters were client-side only (broke pagination), no live feedback during deploy, and no execution metrics on completed runs.
How
task_run_history, BASE_RESULT serialization intrigger_scheduled_run+_mark_failure.renderMarkdown: falsefor JSX toasts,useSearchParamsfor deep-link + pre-fill, polling viasetInterval+getLatestRunStatus, metrics bar in insights panel.Can this PR break any existing features?
Low risk:
scheduled/job) so existing rows are valid post-migration. Serializer usesfields = "__all__"so new columns auto-expose.resultfield was already on the model (never written); now populated. Frontend guards withresult?.total > 0.startswith("Source")convention from the no-code model generator.Database Migrations
backend/backend/core/scheduler/migrations/0002_taskrunhistory_trigger_scope.py— addstrigger,scopecolumns + indexes.Env Config
None.
Relevant Docs
None.
Related Issues or PRs
Dependencies Versions
No changes.
Notes on Testing
Tested locally (gunicorn + Celery worker + React dev server):
Checklist
##Screenhots
##Deep-link toast — clickable "View in Run History →" link in Quick Deploy success toast
##Deploy polling — "Deploying…" spinner on Quick Deploy button

##Completion toast — "Deploy Completed" / "Deploy Failed" toast after polling
##Runtime metrics — metrics bar in Run History expanded row
##Pre-fill create-job — auto-opened drawer with model pre-checked
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code