Skip to content

feat: Deploy & Run History follow-ups — polling, metrics, deep-links, DB columns, server-side filters#64

Merged
abhizipstack merged 21 commits intomainfrom
feat/deploy-runhistory-followups
Apr 17, 2026
Merged

feat: Deploy & Run History follow-ups — polling, metrics, deep-links, DB columns, server-side filters#64
abhizipstack merged 21 commits intomainfrom
feat/deploy-runhistory-followups

Conversation

@abhizipstack
Copy link
Copy Markdown
Contributor

@abhizipstack abhizipstack commented Apr 16, 2026

What

Implements 6 of 7 items from the deferred follow-ups ticket, plus bug fixes found during testing.

Deep-link toast → Run History (#5)

  • Quick Deploy success toast now includes a clickable "View in Run History →" link (with renderMarkdown: false so JSX renders correctly).
  • Run History page auto-expands the most recent run when arriving via deep-link.

Pre-fill create-job from 0-candidates (#6)

  • "Go to Scheduler" CTA navigates to /project/job/list?create=1&project=<pid>&model=<name>.
  • Jobs List reads params: auto-opens the create drawer with the model pre-enabled in Model Configuration.

First-class trigger + scope DB columns (#4)

  • Added trigger (scheduled/manual) and scope (job/model) as real CharField columns on TaskRunHistory with DB indexes.
  • Migration 0002_taskrunhistory_trigger_scope.
  • trigger_scheduled_run writes both columns and kwargs (backward compat).
  • Frontend getRunTriggerScope prefers top-level fields, falls back to kwargs for pre-migration rows.

Server-side Run History filtering (#7)

  • task_run_history endpoint accepts ?trigger=, ?scope=, ?status= query params.
  • Frontend filter changes now trigger a server-side refetch instead of client-side filtering, so pagination is accurate across all pages.

Live deploy progress polling (#3)

  • After dispatch, Quick Deploy button flips to "Deploying…" with spinner.
  • Polls latest run status every 5s via getLatestRunStatus.
  • On terminal state: completion toast (success/failure) with Run History link, explorer refresh, recent-runs cache clear.
  • Auto-cleans on unmount.

Runtime metrics in Run History (#1)

  • After DAG execution (success or failure), trigger_scheduled_run serializes BASE_RESULT into TaskRunHistory.result as JSON.
  • Per-model name, status, end_status; aggregate total/passed/failed counts.
  • Source classes (e.g. SourceMdoela) filtered out — only user-created models appear.
  • Frontend insights panel renders a metrics bar when result.total > 0.

Deferred to separate tickets:

  • User-facing activity logs → OR-1462
  • Row counts per model → OR-1461

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

  • Backend: migration for trigger/scope columns, query-param filtering in task_run_history, BASE_RESULT serialization in trigger_scheduled_run + _mark_failure.
  • Frontend: renderMarkdown: false for JSX toasts, useSearchParams for deep-link + pre-fill, polling via setInterval + getLatestRunStatus, metrics bar in insights panel.

Can this PR break any existing features?

Low risk:

  • New DB columns have defaults (scheduled / job) so existing rows are valid post-migration. Serializer uses fields = "__all__" so new columns auto-expose.
  • Server-side filtering is additive — no params = no filter = same behavior as before.
  • Polling is self-contained in component state; auto-cleans on unmount.
  • result field was already on the model (never written); now populated. Frontend guards with result?.total > 0.
  • Source class filtering uses startswith("Source") convention from the no-code model generator.

Database Migrations

  • backend/backend/core/scheduler/migrations/0002_taskrunhistory_trigger_scope.py — adds trigger, scope columns + 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):

  1. Quick Deploy → success toast shows clickable "View in Run History →" link → navigates to Run History with job preselected + first run auto-expanded.
  2. Quick Deploy on model with no job → "Go to Scheduler" → Jobs List opens with create drawer, model pre-checked.
  3. Run History filters (Trigger/Scope/Status) now refetch from server — verified pagination accuracy.
  4. Quick Deploy → button shows "Deploying…" spinner → polls → completion toast appears on SUCCESS/FAILURE.
  5. Run History expanded row shows metrics bar: "1 model attempted · 1 passed · 0 failed · Mdoela (OK)". Source classes filtered out.
  6. Migration applied cleanly; old runs render with kwargs fallback.

Checklist

##Screenhots
##Deep-link toast — clickable "View in Run History →" link in Quick Deploy success toast

Screenshot 2026-04-17 at 2 01 07 PM

##Deploy polling — "Deploying…" spinner on Quick Deploy button
Screenshot 2026-04-17 at 2 01 07 PM (1)

##Completion toast — "Deploy Completed" / "Deploy Failed" toast after polling

completion_toast

##Runtime metrics — metrics bar in Run History expanded row

Screenshot 2026-04-17 at 2 01 42 PM

##Pre-fill create-job — auto-opened drawer with model pre-checked

Screenshot 2026-04-17 at 2 02 13 PM Screenshot 2026-04-17 at 2 02 32 PM

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

abhizipstack and others added 12 commits April 16, 2026 17:53
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>
@abhizipstack abhizipstack requested review from a team as code owners April 16, 2026 13:40
Comment thread frontend/src/ide/editor/no-code-model/no-code-model.jsx Fixed
Comment thread frontend/src/ide/editor/no-code-model/no-code-model.jsx Fixed
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 16, 2026

Greptile Summary

This PR implements 6 follow-up features for the Deploy & Run History system: deep-link toasts, pre-fill create-job from 0-candidates, first-class trigger/scope DB columns, server-side Run History filtering, live deploy-progress polling, and runtime metrics in the expanded run row. Previous review concerns are all addressed in the merge commits.

  • P1 — scope set to model name in _dispatch_task_run (views.py:654): scope = models_override[0] assigns the actual model name string rather than \"model\" to every JobTriggered event fired from the Quick Deploy path.
  • P2 — polling tracks latest run, not the dispatched run (no-code-model.jsx): a concurrent scheduled run can cause the completion toast to reflect the wrong execution.

Confidence Score: 4/5

Safe to merge after fixing the one-line scope bug in _dispatch_task_run.

One P1 bug remains: wrong scope value in JobTriggered events. Fix is a one-liner. All prior review threads are resolved.

backend/backend/core/scheduler/views.py — scope calculation on line 654 in _dispatch_task_run.

Important Files Changed

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})
Loading

Comments Outside Diff (1)

  1. backend/backend/core/scheduler/views.py, line 654 (link)

    P1 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.

    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.

    Fix in Claude Code

Fix All in Claude Code

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

Comment thread backend/backend/core/scheduler/celery_tasks.py
Comment thread frontend/src/ide/scheduler/JobList.jsx
…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>
@abhizipstack
Copy link
Copy Markdown
Contributor Author

All 6 issues addressed in 3c44484:

  1. Filters dropped on pagination (P1)handlePagination now passes active filterQueries to getRunHistoryList.
  2. BASE_RESULT stale global (P1) — Added snapshot-then-clear pattern + _clear_base_result() helper called in all exit paths (success, timeout, exception).
  3. handleRefresh stale closure (P2) — Updated deps to include filterQueries, currentPage, pageSize, getRunHistoryList.
  4. project URL param ignored (P2)JobList now reads project from URL params and forwards prefillProject to JobDeploy, which pre-selects it.
  5. list_recent_runs_for_model kwargs (P2) — Now prefers run.trigger/run.scope DB columns, falling back to kwargs for pre-migration rows.
  6. CodeQL XSS (DOM text as HTML) — Applied encodeURIComponent() to taskId in toast deep-link URLs.

Comment thread backend/backend/core/scheduler/celery_tasks.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread frontend/src/ide/run-history/Runhistory.jsx
@wicky-zipstack
Copy link
Copy Markdown
Contributor

wicky-zipstack commented Apr 16, 2026

A few items to consider:

1. Same repr(cls) parsing anti-pattern as #54 / #59celery_tasks.py:47

def _clean_name(raw):
    if "'" in raw:
        return raw.split("'")[1].split(".")[-1]

PR #59 already fixed this with cls.__name__ elsewhere. Better to write a clean name into BASE_RESULT.node_name upstream and parse zero times.

2. _clean_name duplicated — defined in both trigger_scheduled_run and _mark_failure. Extract to module level.

3. Polling has no max duration / backoffno-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. RETRY status not handled in terminal listno-code-model.jsx:329 — only ["SUCCESS", "FAILURE", "REVOKED"]. Button stays "Deploying…" during retries — fine if intentional, worth confirming.

5. BASE_RESULT is a module-level global — works for single-threaded Celery worker. If you ever run prefork pool with concurrency > 1 in same worker, the global will be shared/contaminated across concurrent jobs. Worth verifying worker config.

6. startswith("Source") filter is brittlecelery_tasks.py:54. A user-named SourceData model would be hidden. Use a flag on the node instead. (Tech debt — fine for now.)

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>
Copy link
Copy Markdown
Contributor

@tahierhussain tahierhussain left a comment

Choose a reason for hiding this comment

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

@abhizipstack Please add screenshots for the UI changes addressed in this PR.

Comment thread frontend/src/ide/run-history/Runhistory.jsx
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>
Comment thread frontend/src/ide/run-history/Runhistory.jsx
abhizipstack and others added 2 commits April 17, 2026 17:22
…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>
Copy link
Copy Markdown
Contributor

@tahierhussain tahierhussain left a comment

Choose a reason for hiding this comment

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

LGTM

Comment thread frontend/src/ide/scheduler/JobDeploy.jsx
…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>
@abhizipstack abhizipstack merged commit 9104be8 into main Apr 17, 2026
8 checks passed
@abhizipstack abhizipstack deleted the feat/deploy-runhistory-followups branch April 17, 2026 12:59
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.

4 participants