feat: Deploy, Jobs & Run History UX improvements#61
Conversation
|
| Filename | Overview |
|---|---|
| frontend/src/ide/run-history/Runhistory.jsx | Expanded row insights panel for all completed statuses, deep-link support via useSearchParams, and human-readable timestamps — but the onRow indicator hardcodes token.colorError for every expanded row (P1 for SUCCESS/RETRY/REVOKED), and handleRefresh closes over a stale getRunHistoryList (P2). |
| frontend/src/ide/scheduler/JobListTable.jsx | Clickable job name links to Run History, corrected Schedule / Last Run Status columns, and locale-formatted date cells — but the Schedule column calls getTooltipText without a null guard; passes {} when periodic data is absent, causing cronstrue to throw and crash the table render. |
| backend/backend/core/scheduler/views.py | Adds _compute_next_run_time to derive next run from PeriodicTask schedule on the fly; correctly applied in _serialize_task and task_run_history. |
| frontend/src/common/helpers.js | Adds getRelativeTime (extended for future dates) and formatDateTime helpers; both implementations are correct and backward-compatible. |
| frontend/src/ide/scheduler/JobDeploy.jsx | Adds 'Create new' env link (opens in new tab with noopener) and a refresh button for the environment dropdown; no issues found. |
| frontend/src/ide/scheduler/ModelConfigsTable.jsx | Adds materialization option descriptions, column help tooltip, and the { force: true } fix for the stale-closure Refresh bug — implementation looks correct. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Jobs List Page] -->|Click job name link| B["Navigate to /project/job/history?task=<id>"]
B --> C[Run History mounts]
C --> D{URL has ?task param?}
D -->|Yes| E[Match job in list, preselect it]
D -->|No| F[Select first job]
E --> G[getRunHistoryList called]
F --> G
G --> H[Render run table]
H --> I{Row status}
I -->|SUCCESS / FAILURE / RETRY / REVOKED| J[Row is expandable]
I -->|STARTED / RUNNING / PENDING| K[Row not expandable]
J -->|User expands| L[expandedRowRender — status-colored panel]
M[handleJobChange] -->|Updates ?task in URL| B
N[Refresh button] -->|handleRefresh| G
Comments Outside Diff (1)
-
frontend/src/ide/run-history/Runhistory.jsx, line 523-531 (link)onRowalways applies the error color to expanded rowsEvery expanded row gets an inset red left-border (
token.colorError) regardless of its status. Since this PR extends expandability to SUCCESS, RETRY, and REVOKED rows, a green SUCCESS row that is expanded will show a red left indicator — directly contradicting the status-colored header panel rendered insideexpandedRowRender.STATUS_METAis already in scope; use it here:Prompt To Fix With AI
This is a comment left during a code review. Path: frontend/src/ide/run-history/Runhistory.jsx Line: 523-531 Comment: **`onRow` always applies the error color to expanded rows** Every expanded row gets an inset red left-border (`token.colorError`) regardless of its status. Since this PR extends expandability to SUCCESS, RETRY, and REVOKED rows, a green SUCCESS row that is expanded will show a red left indicator — directly contradicting the status-colored header panel rendered inside `expandedRowRender`. `STATUS_META` is already in scope; use it here: 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: frontend/src/ide/run-history/Runhistory.jsx
Line: 523-531
Comment:
**`onRow` always applies the error color to expanded rows**
Every expanded row gets an inset red left-border (`token.colorError`) regardless of its status. Since this PR extends expandability to SUCCESS, RETRY, and REVOKED rows, a green SUCCESS row that is expanded will show a red left indicator — directly contradicting the status-colored header panel rendered inside `expandedRowRender`.
`STATUS_META` is already in scope; use it here:
```suggestion
onRow={(record) =>
expandedRowKeys.includes(record.id)
? {
style: {
boxShadow: `inset 3px 0 0 0 ${
STATUS_META[record.status]?.color ?? token.colorError
}`,
},
}
: {}
}
```
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/scheduler/JobListTable.jsx
Line: 165-180
Comment:
**`getTooltipText` will throw when periodic schedule data is absent**
If `periodic_task_details[task_type]` is `null` (e.g. when the linked `PeriodicTask` row was deleted and the FK is SET_NULL), the `?? {}` fallback passes an empty object to `getTooltipText`, which calls `cronstrue.toString(undefined)` and throws `Error: Expression cannot be null` — crashing the entire Table render.
The equivalent code in `Runhistory.jsx` guards with `if (taskDetails)` before calling `getTooltipText`. Apply the same pattern here:
```suggestion
render: (_, record) => {
const taskDetails =
record.periodic_task_details?.[record.task_type];
const scheduleText = taskDetails
? getTooltipText(taskDetails, record.task_type)
: record.task_type === "interval"
? "Interval"
: "Cron";
return (
<Tooltip title={scheduleText}>
<Tag color="geekblue" icon={<CalendarOutlined />}>
{record.task_type === "interval" ? "Interval" : "Cron"}
</Tag>
<Typography.Text
type="secondary"
style={{ fontSize: 12, marginLeft: 4 }}
>
{scheduleText}
</Typography.Text>
</Tooltip>
);
},
```
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/run-history/Runhistory.jsx
Line: 249-253
Comment:
**`handleRefresh` closes over a potentially stale `getRunHistoryList`**
`getRunHistoryList` is a `useCallback` that depends on `[currentPage, pageSize]`, so its reference changes whenever the user navigates pages. `handleRefresh` only lists `filterQueries.job` as a dep, so it never picks up the newer `getRunHistoryList`. Clicking Refresh after navigating to page 2+ will silently call the version with page-1 defaults, snapping the view back to the first page.
Add `getRunHistoryList` to the dependency array:
```suggestion
const handleRefresh = useCallback(() => {
if (filterQueries.job) {
getRunHistoryList(filterQueries.job);
}
}, [filterQueries.job, getRunHistoryList]);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (5): Last reviewed commit: "fix: wrap getRunHistoryList in useCallba..." | Re-trigger Greptile
wicky-zipstack
left a comment
There was a problem hiding this comment.
LGTM ✅
Nits (non-blocking):
- views.py — 3 blank lines before
_compute_next_run_time(should be 2). try/except Exceptionin_compute_next_run_timeswallows silently — considerlogger.debug(...).STATUS_METAin Runhistory doesn't includeRUNNING(onlySTARTED) — falls through toPENDING.getRunInsightsoverlaps withgetRunTriggerScopein #62 — dedupe once both land.
- Job name cell is now a History-icon link that navigates to
/project/job/history?task=<user_task_id>, preselecting that job's
run history.
- "Schedule Type" previously rendered task_status (SUCCESS / FAILED)
which conflated schedule-type with last-run-status. Renamed to
"Schedule" and renders the actual schedule ("every 1 hours" /
cron expression) behind a cron-vs-interval tag. Added a new
"Last Run Status" column for the run-status part.
- Last Run / Next Run columns render a local-formatted datetime plus
a muted relative time underneath; native title holds the ISO. Next
Run now reads from next_run_time (new field on the API) rather than
task_run_time which was the last-start time mistakenly shown as
"Next Run".
- Run History page accepts ?task=<id> on mount and writes the param
back when the user changes the selector, so deep-links are
shareable.
- helpers.js: getRelativeTime handles future dates ("in 2h") and a
new formatDateTime helper returns "Oct 14, 2026, 3:25 PM" style.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Materialization dropdown options now include detailed descriptions (when to pick each, implications) rendered inline in the popup, not just a single-line hint. Column header gains an info tooltip with a TABLE / VIEW / INCREMENTAL summary plus a note on switching materialization later. - Environment field gets a "Create new" link (opens /project/env/list in a new tab so unsaved job-form state isn't lost) and a refresh icon button to reload the env list after creation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Refresh button on an expanded Incremental model panel appeared to
do nothing useful — dropdowns wouldn't repopulate. Root cause was a
stale-closure race:
setColumnCache((prev) => { delete entry; return rest; })
fetchColumnsForModel(modelName)
The fetchColumnsForModel reference captured by onClick had the
pre-deletion columnCache in its closure, so its early-return
"if (columnCache[modelName]) return" fired against the old snapshot
and the refetch never ran. The cache got cleared but never refilled.
Added a { force: true } option to fetchColumnsForModel that bypasses
the cache check. Refresh now calls it with force=true and a single
network round-trip repopulates the column caches. Avoids the
setState + stale-closure interaction entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expandable rows now cover more than just FAILURE. Clicking the expander on any SUCCESS / FAILURE / RETRY / REVOKED run opens an insights panel that reads as an extension of the row: - Status header with a tinted icon (green check for success, red cross for failure, etc.), start_time, and duration. - Scope tag (Single model vs Full job) plus the list of models attempted. Model list is derived from kwargs.models_override for scoped runs or from kwargs.model_configs.enabled for full runs. Back-compat handling for legacy kwargs.source === "quick_deploy". - For FAILURE runs, the error_message renders in a preformatted, scrollable Alert so stack traces preserve whitespace and don't blow out the page. - Whole panel uses theme tokens (colorSuccessBg / colorErrorBg / colorInfoBg etc.) so light and dark both track. Runtime-captured metrics (rows processed, tables written, schemas touched) are not yet surfaced because the scheduler pipeline doesn't persist them to TaskRunHistory.result today. Documented in the PR for follow-up instrumentation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Triggered column was rendering the raw ISO timestamp straight
from the API, which is hard to read at a glance. Now the cell shows a
locale-formatted date ("Oct 14, 2026, 3:25 PM") with a muted relative
time below ("2h ago" / "in 3h"); the full ISO sits in a native
tooltip for machine-precise inspection. The expanded insights panel's
header reuses the same formatting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_META
P1: next_run_time was exposed in the API but never written, so the
"Next Run" column was always empty. Added _compute_next_run_time
that uses celery's remaining_estimate on the PeriodicTask's
schedule to derive the next run from the last run time. Falls back
to task.next_run_time if it happens to be set directly.
P2: STATUS_META was rebuilt on every render inside the component body.
Wrapped in useMemo with [token] as the dependency so it only
recomputes when the antd theme changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…osure
P1: _compute_next_run_time added remaining to reference (last_run_at)
instead of now, giving wrong results for interval jobs. Fixed to
use timezone.now() + remaining. Added logger.debug on failure.
P1: getTooltipText crashed when a job had no linked periodic task.
Guarded with optional chaining on periodic_task_details.
P2: Auto-expand useEffect used JobHistoryData as dep, resetting
user-expanded rows on every filter change. Switched to backUpData
so it only fires on fresh data loads.
P2: handleJobChange useCallback was missing getRunHistoryList in its
dependency array, risking stale closure. Added to deps.
Nit: Added RUNNING key to STATUS_META so backends that send RUNNING
instead of STARTED render correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13ccd7a to
70ae80b
Compare
getRunHistoryList was defined directly in the component body, causing handleJobChange's useCallback to recreate on every render since its dependency changed each time. Wrapped in useCallback with explicit deps so handleJobChange gets a stable reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
What
Jobs List Page
/project/job/history?task=<user_task_id>, deep-linking to that job's Run History.task_statuslike SUCCESS/FAILED) into two correct columns:next_run_timein the jobs-list API response; previously the "Next Run" column mistakenly readtask_run_time(last start time).Create Job Form
/project/env/listin a new tab to preserve unsaved form state) and a refresh button to reload the env list after creating one.fetchColumnsForModelcallback captured the pre-deletioncolumnCachesnapshot and returned early without refetching. Now uses{ force: true }to bypass the cache check.Run History Page
kwargs.models_overrideorkwargs.model_configs).Alert.?task=<id>from the URL on mount and writes it back on job-select change, enabling sharable deep-links from Jobs List.Shared helpers (
frontend/src/common/helpers.js)getRelativeTimeextended to handle future dates ("in 2h", "in 3d").formatDateTimehelper for consistent locale-formatted timestamps.Why
Feedback from staging testing identified multiple UX gaps:
How
Button type="link"withuseNavigatefor clickable job names;getTooltipTextfor schedule display;formatDateTime+getRelativeTimefor dates; backend addsnext_run_timeto the list serializer.MATERIALIZATION_OPTIONSdescriptions enriched;MATERIALIZATION_COLUMN_HELPrendered inside aTooltipon the header;PlusOutlined+ReloadOutlinedon the Environment label. Stale-closure fix viaforceparam onfetchColumnsForModel.STATUS_METAmaps each status to theme-token-driven icon/color/bg;getRunInsightsderives scope and model list fromkwargswith back-compat for legacykwargs.source;useSearchParamsfor deep-link support.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:
next_run_timeis a new field on the API response; existing consumers that don't read it are unaffected.getRelativeTimeis backward-compatible: existing callers that only pass past dates get identical output. The future-date handling is new behavior for new callers only.rowExpandablereturning false for SUCCESS rows — no known consumer does.Database Migrations
None.
Env Config
None.
Relevant Docs
None.
Related Issues or PRs
Dependencies Versions
No changes.
Notes on Testing
Tested locally (host-mode: Django runserver + Celery worker + React dev server, Postgres + Redis via Docker):
next_run_time./project/job/history?task=3→ correct job preselected on mount.Checklist
Screenshots
Jobs List — clickable job name, Schedule column, Last Run Status, human-readable dates
Materialization column info tooltip
Create Job Form — Environment with "Create new" link + refresh
Model Configuration — Materialization dropdowns
Run History — human-readable Triggered column with relative time
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code