Skip to content

feat: add job history to Data Management page#213

Merged
coji merged 1 commit intomainfrom
feat/persist-run-id
Mar 18, 2026
Merged

feat: add job history to Data Management page#213
coji merged 1 commit intomainfrom
feat/persist-run-id

Conversation

@coji
Copy link
Owner

@coji coji commented Mar 18, 2026

リロードするたび 状態が消える
fetcher.dataじゃ この壁超える気ない
SSEを一本 繋いだら見事
ページ跨いでも 生き残るこの仕事

カードに並んだ 履歴奇跡
pending leased 色が移り変わる
プログレスバーが 進捗刻む
Cancelひとつで runの首掴む

RunStatusAlerts 百行壊す
useRunを二本 useRunsに飛ばす
シンプルに直す コードは引き算
足すより削って 本質残す

PRひとつが 静かな戦い
No findings _ 合格味わい
片手にコーヒー 画面にストーリー
#197 Close 俺のトロフィー

Summary

  • Data Management ページに Job History セクションを追加。durably.useRuns() で全ジョブの実行履歴をリアルタイム表示
  • 従来の per-run useRun(runId) + fetcher.data パターンを廃止し、ページレベルの SSE 1本に統合
  • ページリロードしても実行中ジョブの進捗が復元される

Closes #197

Changes

  • +components/job-history.tsx (new): ジョブ履歴リスト。ステータス Badge、進捗バー、Cancel/Retry ボタン、ページネーション
  • index.tsx: loader 追加(organizationId)、useRuns / useRunActions でジョブ一覧取得、RunStatusAlerts 削除、各セクション簡素化

Test plan

  • pnpm validate passes
  • Data Management ページで Refresh 押下 → Job History にリアルタイムで run 出現
  • ページリロード → 実行中 run が引き続き表示される
  • 完了後 completed ステータスと結果表示
  • Cancel / Retry ボタン動作確認

🤖 Generated with Claude Code

Replace per-run SSE tracking (useRun) with page-level durably.useRuns()
so job progress survives page reloads and past executions are visible.

- Add JobHistory component with status badges, progress bars, cancel/retry
- Simplify Refresh/Recalculate sections to trigger-only (no inline status)
- Derive isRunning from runs list instead of fetcher.data runId

Closes #197

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

新しいJobHistoryコンポーネントを追加し、durably統合を通じてデータ管理実行の履歴表示・操作機能を実装。既存のインデックスファイルをリファクタリングして、loaderを追加し、実行トラッキングロジックを簡潔化しました。

Changes

Cohort / File(s) Summary
新規JobHistoryコンポーネント
app/routes/$orgSlug/settings/data-management/+components/job-history.tsx
実行履歴UI専用コンポーネント。isRunActive判定関数、ステータスバッジ、実行アイテム表示、ページネーション制御(次へ/前へ)、キャンセル・再実行アクションボタン、進捗インジケーターとエラー表示機能を提供。
データ管理ページの再構成
app/routes/$orgSlug/settings/data-management/index.tsx
loader関数を追加してorganizationIdを取得。durably.useRunsとdurably.useRunActionsを統合。RunStatusAlertsを廃止しtriggerErrorベースのシンプルな警告表示に統一。JobHistoryコンポーネントを接続してページネーション、キャンセル、再実行機能を有効化。

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant DataManagementPage as DataManagementPage
    participant Loader as Loader<br/>(organizationId)
    participant useRuns as durably.useRuns
    participant JobHistory as JobHistory<br/>Component
    participant useRunActions as durably.useRunActions

    Loader->>DataManagementPage: organizationId
    DataManagementPage->>useRuns: fetch run history
    useRuns->>DataManagementPage: runs, pagination state
    DataManagementPage->>JobHistory: render runs + handlers
    JobHistory->>User: display run list + actions
    
    User->>JobHistory: click Cancel/Retry
    JobHistory->>DataManagementPage: onCancel/onRetrigger
    DataManagementPage->>useRunActions: trigger action
    useRunActions->>DataManagementPage: update status
    DataManagementPage->>JobHistory: refresh state
    JobHistory->>User: updated UI
    
    User->>JobHistory: click Prev/Next
    JobHistory->>DataManagementPage: onPrevPage/onNextPage
    DataManagementPage->>useRuns: load different page
    useRuns->>JobHistory: new runs data
    JobHistory->>User: display new page
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 新たな履歴の道を歩み
ページをめくる喜び
ジョブたちが並んで
ステータスは明らか、キャンセルも自由
durably の力で進捗を追う

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add job history to Data Management page' accurately describes the main change: adding a new Job History component to display job executions on the Data Management page.
Linked Issues check ✅ Passed The PR implements the core requirements from issue #197: uses durably.useRuns() for real-time job tracking, adds JobHistory component with pagination/cancel/retry functionality, and enables progress persistence across page reloads via the SSE stream.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #197 objectives: new job-history.tsx component, index.tsx modifications to integrate JobHistory and use durably APIs, and removal of previous RunStatusAlerts pattern.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/persist-run-id
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/routes/$orgSlug/settings/data-management/+components/job-history.tsx (1)

100-105: 進捗率は 0〜100 にクランプしておくと安全です。

current > total や不正値が入った場合に、プログレス表示が崩れる可能性があります。

🔧 変更例
-              <Progress
-                value={(run.progress.current / run.progress.total) * 100}
-                className="h-1.5"
-              />
+              <Progress
+                value={Math.min(
+                  100,
+                  Math.max(
+                    0,
+                    (run.progress.current / run.progress.total) * 100,
+                  ),
+                )}
+                className="h-1.5"
+              />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/`$orgSlug/settings/data-management/+components/job-history.tsx
around lines 100 - 105, The Progress value calculation can produce values <0,
>100 or NaN (e.g., current > total or bad input); update the code around the
Progress component to compute a safe clamped percent: derive percent =
(run.progress.current / run.progress.total) * 100, ensure it's a finite number
(e.g., via Number.isFinite) and then clamp it with Math.max(0, Math.min(100,
percent)), and pass that clamped value to the Progress value prop so
run.progress.current, run.progress.total and the Progress component always
receive 0–100.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/routes/`$orgSlug/settings/data-management/index.tsx:
- Around line 248-266: isCrawlRunning/isRecalculateRunning are computed from the
paginated runs so switching pages can hide active jobs; call durably.useRuns a
second time (e.g. const { runs: allRuns } = durably.useRuns({...})) with options
that return either all runs or only active runs (filter/active-only pagination)
for the org (use the same labels: { organizationId }) and compute
isCrawlRunning/isRecalculateRunning from that non-paginated/all-active runs
array instead of the paged runs; update references to use allRuns (or a
descriptive name) when computing the active-job booleans.

---

Nitpick comments:
In `@app/routes/`$orgSlug/settings/data-management/+components/job-history.tsx:
- Around line 100-105: The Progress value calculation can produce values <0,
>100 or NaN (e.g., current > total or bad input); update the code around the
Progress component to compute a safe clamped percent: derive percent =
(run.progress.current / run.progress.total) * 100, ensure it's a finite number
(e.g., via Number.isFinite) and then clamp it with Math.max(0, Math.min(100,
percent)), and pass that clamped value to the Progress value prop so
run.progress.current, run.progress.total and the Progress component always
receive 0–100.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c94af72-2aa0-49ea-bc62-50a2cd8121f1

📥 Commits

Reviewing files that changed from the base of the PR and between ee7b9e7 and 33515bf.

📒 Files selected for processing (2)
  • app/routes/$orgSlug/settings/data-management/+components/job-history.tsx
  • app/routes/$orgSlug/settings/data-management/index.tsx

Comment on lines +248 to +266
const { runs, page, hasMore, isLoading, nextPage, prevPage } =
durably.useRuns({
labels: { organizationId },
pageSize: 10,
})

const {
cancel,
retrigger,
isLoading: isActing,
error: actionError,
} = durably.useRunActions()

const isCrawlRunning = runs.some(
(r) => r.jobName === 'crawl' && isRunActive(r.status),
)
const isRecalculateRunning = runs.some(
(r) => r.jobName === 'recalculate' && isRunActive(r.status),
)
Copy link

@coderabbitai coderabbitai bot Mar 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 実行中判定ロジックが現在ページの runs 依存かを確認
rg -n -C3 'const \{ runs, page, hasMore, isLoading, nextPage, prevPage \} =|isCrawlRunning|isRecalculateRunning|runs\.some' 'app/routes/$orgSlug/settings/data-management/index.tsx'

# このファイル内で useRuns が何回使われ、実行中専用の別データソースがあるか確認
rg -n -C2 'useRuns\(' 'app/routes/$orgSlug/settings/data-management/index.tsx'

Repository: coji/upflow

Length of output: 1127


🏁 Script executed:

# durably.useRuns の実装を確認
fd -e ts -e tsx | xargs rg -l 'useRuns' | head -20

Repository: coji/upflow

Length of output: 109


🏁 Script executed:

# durably ライブラリ/モジュールの定義を探す
rg -n 'export.*useRuns|const useRuns|function useRuns' -t ts -t tsx

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# RefreshSection と RecalculateSection がどのように isRunning を使用しているか確認
rg -n -A5 'interface.*RefreshSection|RefreshSection.*isRunning|RecalculateSection.*isRunning' -t ts -t tsx

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# Find durably imports and module location
rg -n 'import.*durably|from.*durably' app/routes/$orgSlug/settings/data-management/index.tsx

Repository: coji/upflow

Length of output: 141


🏁 Script executed:

# Search for durably module definition
fd . -name '*durably*' -o -name '*durable*'

Repository: coji/upflow

Length of output: 285


🏁 Script executed:

# Check the imports at the top of the data-management file
head -50 app/routes/$orgSlug/settings/data-management/index.tsx

Repository: coji/upflow

Length of output: 160


🏁 Script executed:

# Find the data-management index.tsx file
fd 'data-management.*index.tsx'

Repository: coji/upflow

Length of output: 37


🏁 Script executed:

# Find durably-related files
fd durably

Repository: coji/upflow

Length of output: 137


🏁 Script executed:

# List the app/routes directory structure
ls -la app/routes/ | head -20

Repository: coji/upflow

Length of output: 718


🏁 Script executed:

# List $orgSlug directory
ls -la app/routes/'$orgSlug'/

Repository: coji/upflow

Length of output: 732


🏁 Script executed:

# Find data-management directory
fd 'data-management' app/routes

Repository: coji/upflow

Length of output: 100


🏁 Script executed:

# Read the durably.ts file to understand useRuns
cat app/services/durably.ts

Repository: coji/upflow

Length of output: 259


🏁 Script executed:

# Read durably.server.ts to understand available methods
cat app/services/durably.server.ts

Repository: coji/upflow

Length of output: 2570


🏁 Script executed:

# Read the full data-management/index.tsx file
wc -l 'app/routes/$orgSlug/settings/data-management/index.tsx'

Repository: coji/upflow

Length of output: 113


🏁 Script executed:

# Read data-management/index.tsx
cat 'app/routes/$orgSlug/settings/data-management/index.tsx'

Repository: coji/upflow

Length of output: 8939


🏁 Script executed:

# Check JobHistory component for pagination handling
cat 'app/routes/$orgSlug/settings/data-management/+components/job-history.tsx'

Repository: coji/upflow

Length of output: 5533


🏁 Script executed:

# Check if there are other useRuns calls or alternative job status queries in the codebase
rg 'useRuns|useRunActions|getRuns|getActiveRuns' -A 3

Repository: coji/upflow

Length of output: 706


ページング状態に依存した実行中判定の修正が必要です。

isCrawlRunning / isRecalculateRunning が現在ページの runs のみを参照しているため、履歴ページを移動した時に実行中ジョブが見失われます。例えばページ1で実行中の crawl ジョブがページ2に移動すると、runs にはそのジョブが含まれず実行中判定が false になり、リフレッシュボタンが再度有効化されます。

実行中判定をページング非依存のデータで算出してください。useRuns() の別の呼び出しやフィルタリングオプションで全ての(またはアクティブのみの)ジョブを取得し、そこから実行中状態を判定することを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/`$orgSlug/settings/data-management/index.tsx around lines 248 -
266, isCrawlRunning/isRecalculateRunning are computed from the paginated runs so
switching pages can hide active jobs; call durably.useRuns a second time (e.g.
const { runs: allRuns } = durably.useRuns({...})) with options that return
either all runs or only active runs (filter/active-only pagination) for the org
(use the same labels: { organizationId }) and compute
isCrawlRunning/isRecalculateRunning from that non-paginated/all-active runs
array instead of the paged runs; update references to use allRuns (or a
descriptive name) when computing the active-job booleans.

Copy link
Owner Author

Choose a reason for hiding this comment

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

ご指摘ありがとうございます。技術的には正しい指摘ですが、以下の理由で現時点では対応しません:

  • useRunscreatedAt 降順で返すため、アクティブなジョブ(pending/leased)は常にページ0(最新)に位置します
  • サーバー側の concurrencyKey が同一ジョブの重複実行を防止しているため、仮にボタンが再有効化されても実害はありません
  • SSE リアルタイム更新はページ0でのみ有効なので、ユーザーが実行中にページ2以降に遷移するケースはほぼありません

実際に問題が報告された場合に、status フィルタ付きの別 useRuns 呼び出しで対応する予定です。

Copy link

Choose a reason for hiding this comment

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

@coji、ご説明ありがとうございます。おっしゃる通りで、以下の設計上の制約が重なることで実質的なリスクが軽減されていますね:

  • createdAt 降順ページングによりアクティブジョブは常にページ0に存在する
  • concurrencyKey による重複実行防止がセーフティネットになっている

現実的なリスクが低い状況での過剰な最適化を避けるという判断は理にかなっています。問題が実際に報告された際に status フィルタ付き useRuns で対応する方針で承知しました。


✏️ Learnings added
Learnt from: coji
Repo: coji/upflow PR: 213
File: app/routes/$orgSlug/settings/data-management/index.tsx:248-266
Timestamp: 2026-03-18T12:45:03.601Z
Learning: In app/routes/$orgSlug/settings/data-management/index.tsx, isCrawlRunning/isRecalculateRunning are intentionally computed from the paginated runs array (page 0). This is acceptable because: (1) useRuns returns runs in createdAt descending order so active (pending/leased) jobs always appear on page 0; (2) server-side concurrencyKey prevents duplicate job execution even if the button were re-enabled; (3) SSE real-time updates only apply to page 0. Do not flag this pattern as a bug.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: coji/upflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T00:33:07.422Z
Learning: Applies to batch/commands/*.ts : The `crawl` batch job is a durably-managed job that can be interrupted and resumed - it fetches from GitHub, analyzes data, upserts, exports, and triggers classify

Learnt from: coji
Repo: coji/upflow PR: 141
File: app/components/form-dialog.tsx:43-43
Timestamp: 2026-03-09T08:24:49.797Z
Learning: Do not flag missing useCallback/useMemo dependency array entries in review unless Biome (the authoritative linter for dependency exhaustiveness in this repo) detects them. If Biome does not report an issue, treat it as non-actionable for all TSX files in the repository (including app/components/form-dialog.tsx).

@coji coji merged commit 3bd7922 into main Mar 18, 2026
6 checks passed
@coji coji deleted the feat/persist-run-id branch March 18, 2026 12:48
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.

feat: persist runId across page reloads for long-running durably jobs

1 participant