feat: add job history to Data Management page#213
Conversation
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]>
📝 WalkthroughWalkthrough新しいJobHistoryコンポーネントを追加し、durably統合を通じてデータ管理実行の履歴表示・操作機能を実装。既存のインデックスファイルをリファクタリングして、loaderを追加し、実行トラッキングロジックを簡潔化しました。 Changes
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
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
app/routes/$orgSlug/settings/data-management/+components/job-history.tsxapp/routes/$orgSlug/settings/data-management/index.tsx
| 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), | ||
| ) |
There was a problem hiding this comment.
🧩 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 -20Repository: coji/upflow
Length of output: 109
🏁 Script executed:
# durably ライブラリ/モジュールの定義を探す
rg -n 'export.*useRuns|const useRuns|function useRuns' -t ts -t tsxRepository: coji/upflow
Length of output: 82
🏁 Script executed:
# RefreshSection と RecalculateSection がどのように isRunning を使用しているか確認
rg -n -A5 'interface.*RefreshSection|RefreshSection.*isRunning|RecalculateSection.*isRunning' -t ts -t tsxRepository: 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.tsxRepository: 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.tsxRepository: 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 durablyRepository: coji/upflow
Length of output: 137
🏁 Script executed:
# List the app/routes directory structure
ls -la app/routes/ | head -20Repository: 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/routesRepository: coji/upflow
Length of output: 100
🏁 Script executed:
# Read the durably.ts file to understand useRuns
cat app/services/durably.tsRepository: coji/upflow
Length of output: 259
🏁 Script executed:
# Read durably.server.ts to understand available methods
cat app/services/durably.server.tsRepository: 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 3Repository: 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.
There was a problem hiding this comment.
ご指摘ありがとうございます。技術的には正しい指摘ですが、以下の理由で現時点では対応しません:
useRunsはcreatedAt降順で返すため、アクティブなジョブ(pending/leased)は常にページ0(最新)に位置します- サーバー側の
concurrencyKeyが同一ジョブの重複実行を防止しているため、仮にボタンが再有効化されても実害はありません - SSE リアルタイム更新はページ0でのみ有効なので、ユーザーが実行中にページ2以降に遷移するケースはほぼありません
実際に問題が報告された場合に、status フィルタ付きの別 useRuns 呼び出しで対応する予定です。
There was a problem hiding this comment.
@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).
Summary
durably.useRuns()で全ジョブの実行履歴をリアルタイム表示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 validatepasses🤖 Generated with Claude Code