Skip to content

feat: Deploy, Jobs & Run History UX improvements#61

Merged
abhizipstack merged 8 commits intomainfrom
feat/deploy-jobs-runhistory-improvements
Apr 16, 2026
Merged

feat: Deploy, Jobs & Run History UX improvements#61
abhizipstack merged 8 commits intomainfrom
feat/deploy-jobs-runhistory-improvements

Conversation

@abhizipstack
Copy link
Copy Markdown
Contributor

@abhizipstack abhizipstack commented Apr 16, 2026

What

Jobs List Page

  • Job name cell is now a clickable link (with HistoryOutlined icon) that navigates to /project/job/history?task=<user_task_id>, deep-linking to that job's Run History.
  • Renamed misleading "Schedule Type" column (which displayed task_status like SUCCESS/FAILED) into two correct columns:
    • Schedule — shows the actual cron expression or interval text behind a Cron/Interval tag.
    • Last Run Status — shows the run-status tag (SUCCESS, FAILED, etc.) where it was previously hidden.
  • Last Run and Next Run columns render locale-formatted date ("Oct 14, 2026, 3:25 PM") with a muted relative-time subtitle ("2h ago" / "in 3h") and an ISO tooltip on hover.
  • Backend now exposes next_run_time in the jobs-list API response; previously the "Next Run" column mistakenly read task_run_time (last start time).

Create Job Form

  • Materialization dropdown options now show detailed per-option descriptions inline in the popup (when to pick TABLE vs VIEW vs INCREMENTAL, implications of switching later). Column header gets an info-circle tooltip with a summary.
  • Environment field gets a "Create new" link (opens /project/env/list in a new tab to preserve unsaved form state) and a refresh button to reload the env list after creating one.
  • Fixed the Incremental "Refresh" button bug: clicking it used to silently no-op because of a stale-closure race — the fetchColumnsForModel callback captured the pre-deletion columnCache snapshot and returned early without refetching. Now uses { force: true } to bypass the cache check.
  • Cron & Interval "tabs": already implemented as a single Segmented toggle in one Collapse pane in the existing code — verified as addressed.

Run History Page

  • Expanded row now opens for all completed statuses (SUCCESS, FAILURE, RETRY, REVOKED), not just failures:
    • Shows a status-colored header with icon, locale-formatted triggered time (with relative time), and duration.
    • Scope tag (Single model / Full job) plus the list of models attempted (derived from kwargs.models_override or kwargs.model_configs).
    • For FAILURE rows: the error_message renders in a scrollable preformatted Alert.
    • All colors go through antd theme tokens for light/dark compatibility.
  • Triggered column now renders human-readable dates (same format as Jobs List) instead of raw ISO strings.
  • Run History page reads ?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)

  • getRelativeTime extended to handle future dates ("in 2h", "in 3d").
  • New formatDateTime helper for consistent locale-formatted timestamps.

Why

Feedback from staging testing identified multiple UX gaps:

  • Users couldn't navigate from Jobs to the corresponding Run History without manually switching pages and selecting the job.
  • The "Schedule Type" column was misleading — it showed run status, not schedule type.
  • Raw ISO timestamps were hard to read at a glance.
  • No materialization guidance in the job form — users didn't know which to pick or what happens when they switch.
  • The Incremental Refresh button silently failed, leaving dropdowns empty.
  • Completed runs (including successes) had no insights panel; only failures showed an expanded row.

How

  • Jobs List: antd Button type="link" with useNavigate for clickable job names; getTooltipText for schedule display; formatDateTime + getRelativeTime for dates; backend adds next_run_time to the list serializer.
  • Create Job Form: MATERIALIZATION_OPTIONS descriptions enriched; MATERIALIZATION_COLUMN_HELP rendered inside a Tooltip on the header; PlusOutlined + ReloadOutlined on the Environment label. Stale-closure fix via force param on fetchColumnsForModel.
  • Run History: STATUS_META maps each status to theme-token-driven icon/color/bg; getRunInsights derives scope and model list from kwargs with back-compat for legacy kwargs.source; useSearchParams for 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:

  • Jobs List column restructure is purely additive — "Schedule Type" is renamed to "Schedule" and "Last Run Status" is split out. No data model change; only UI column layout.
  • next_run_time is a new field on the API response; existing consumers that don't read it are unaffected.
  • getRelativeTime is backward-compatible: existing callers that only pass past dates get identical output. The future-date handling is new behavior for new callers only.
  • Incremental Refresh fix is behavior-correcting: previously it was a no-op, now it refetches. No downstream side effects.
  • Run History expandable row widens from FAILURE-only to all completed statuses — SUCCESS rows that were previously not expandable now are. Existing FAILURE rendering is preserved (error_message still shown in Alert). The only risk is if a consumer relied on rowExpandable returning 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):

  1. Jobs List: clicked job name → navigated to Run History with correct job preselected. Verified Schedule column shows cron/interval text. Last Run / Next Run render "Apr 15, 2026, 3:25 PM / 2h ago" style. Next Run reads from next_run_time.
  2. Create Job Form: opened materialization dropdown → per-option help text visible. Hovered info icon on Materialization column header → tooltip with full TABLE / VIEW / INCREMENTAL guidance. Clicked "Create new" env → opened /project/env/list in new tab. Clicked refresh → env list reloaded.
  3. Incremental Refresh bug: expanded an INCREMENTAL model row, clicked Refresh → columns refetched and dropdowns repopulated (previously they went empty).
  4. Run History: expanded a SUCCESS row → insights panel showed green header, duration, scope tag, model list. Expanded FAILURE row → same header (red) plus scrollable error trace in Alert. Triggered column formatted with locale date + relative time.
  5. Deep-link: navigated to /project/job/history?task=3 → correct job preselected on mount.
  6. Dark theme: expanded panels, date cells, materialization tooltips all render correctly in dark mode via theme tokens.

Checklist

Screenshots

Jobs List — clickable job name, Schedule column, Last Run Status, human-readable dates

Screenshot 2026-04-16 at 2 55 10 PM

Materialization column info tooltip

Screenshot 2026-04-16 at 2 56 26 PM

Create Job Form — Environment with "Create new" link + refresh

Screenshot 2026-04-16 at 2 56 33 PM

Model Configuration — Materialization dropdowns

Screenshot 2026-04-16 at 2 56 55 PM

Run History — human-readable Triggered column with relative time

Screenshot 2026-04-16 at 2 57 32 PM

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 16, 2026

Greptile Summary

This PR delivers a solid set of UX improvements across the Jobs List, Create Job Form, and Run History pages: clickable job-name deep-links, corrected Schedule/Last Run Status columns, human-readable timestamps, materialization guidance, a working Incremental Refresh, and per-status expanded rows. Two issues need attention before merging:

  • P1 — onRow left-border always red (Runhistory.jsx line 523): expanded SUCCESS/RETRY/REVOKED rows show token.colorError (red) instead of their status color, contradicting the per-status panel header just below.
  • P1 — getTooltipText crash (JobListTable.jsx line 165): the ?? {} fallback for missing periodic-task data passes {} to cronstrue.toString(undefined), throwing an unhandled error that crashes the Schedule column; Runhistory.jsx has a proper if (taskDetails) guard that should be mirrored here.

Confidence Score: 4/5

Two P1 bugs in Runhistory.jsx and JobListTable.jsx need fixing before merge; backend and helper changes are clean.

Prior reviewer concerns (next_run_time, STATUS_META memoization, stale closure) are addressed. Two new issues remain: the onRow color regression affects all non-FAILURE expanded rows, and the unguarded getTooltipText call can crash the Jobs List table for any task with a missing periodic schedule.

frontend/src/ide/run-history/Runhistory.jsx (onRow color + handleRefresh deps), frontend/src/ide/scheduler/JobListTable.jsx (getTooltipText null guard)

Important Files Changed

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=&lt;id&gt;"]
    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
Loading

Comments Outside Diff (1)

  1. frontend/src/ide/run-history/Runhistory.jsx, line 523-531 (link)

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

    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.

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

Comment thread backend/backend/core/scheduler/views.py Outdated
Comment thread frontend/src/ide/run-history/Runhistory.jsx Outdated
Copy link
Copy Markdown
Contributor

@wicky-zipstack wicky-zipstack left a comment

Choose a reason for hiding this comment

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

LGTM ✅

Nits (non-blocking):

  • views.py — 3 blank lines before _compute_next_run_time (should be 2).
  • try/except Exception in _compute_next_run_time swallows silently — consider logger.debug(...).
  • STATUS_META in Runhistory doesn't include RUNNING (only STARTED) — falls through to PENDING.
  • getRunInsights overlaps with getRunTriggerScope in #62 — dedupe once both land.

Comment thread backend/backend/core/scheduler/views.py
Comment thread frontend/src/ide/run-history/Runhistory.jsx
Comment thread frontend/src/ide/run-history/Runhistory.jsx
Comment thread frontend/src/ide/run-history/Runhistory.jsx Outdated
Comment thread backend/backend/core/scheduler/views.py
Comment thread frontend/src/ide/run-history/Runhistory.jsx
Comment thread frontend/src/ide/run-history/Runhistory.jsx
Comment thread frontend/src/ide/scheduler/JobDeploy.jsx
abhizipstack and others added 7 commits April 16, 2026 17:19
- 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>
@abhizipstack abhizipstack force-pushed the feat/deploy-jobs-runhistory-improvements branch from 13ccd7a to 70ae80b Compare April 16, 2026 11:58
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>
Comment thread frontend/src/ide/scheduler/JobListTable.jsx
@abhizipstack abhizipstack requested review from tahierhussain and removed request for tahierhussain April 16, 2026 12:07
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

@abhizipstack abhizipstack merged commit f4e9081 into main Apr 16, 2026
8 checks passed
@abhizipstack abhizipstack deleted the feat/deploy-jobs-runhistory-improvements branch April 16, 2026 12:18
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