Add job status endpoint, persist lastJobId; replace in-process queue with BullMQ-based queue and worker; link job status in UI
This commit is contained in:
@@ -13,6 +13,7 @@ interface LoaderData {
|
||||
bars: any[];
|
||||
timeframe: string;
|
||||
range: string;
|
||||
stockRecord?: any;
|
||||
}
|
||||
|
||||
const TIMEFRAMES = [
|
||||
@@ -49,6 +50,17 @@ export async function loader({ params, request }: { params: { ticker: string };
|
||||
let position = null;
|
||||
let orders = [];
|
||||
let bars = [];
|
||||
let stockRecord: any = null;
|
||||
let stockRecord: any = null;
|
||||
try {
|
||||
const stockRes = await fetch(`${baseUrl}/api/stocks`);
|
||||
if (stockRes.ok) {
|
||||
const list = await stockRes.json();
|
||||
stockRecord = list.find((s: any) => s.ticker === ticker) || null;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch positions
|
||||
@@ -65,6 +77,17 @@ export async function loader({ params, request }: { params: { ticker: string };
|
||||
const barsRes = await fetch(`${baseUrl}/api/alpaca/quote/${ticker}?timeframe=${timeframe}&range=${range}`);
|
||||
const barsData = barsRes.ok ? await barsRes.json() : null;
|
||||
bars = barsData?.bars || [];
|
||||
|
||||
// Fetch stock record (to get lastJobId)
|
||||
try {
|
||||
const stockRes = await fetch(`${baseUrl}/api/stocks`);
|
||||
if (stockRes.ok) {
|
||||
const list = await stockRes.json();
|
||||
stockRecord = list.find((s: any) => s.ticker === ticker) || null;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`analyze/${ticker}: loader error`, err);
|
||||
}
|
||||
@@ -73,7 +96,7 @@ export async function loader({ params, request }: { params: { ticker: string };
|
||||
}
|
||||
|
||||
export default function StockDetail() {
|
||||
const { ticker, position, orders, bars, timeframe, range } = useLoaderData() as LoaderData;
|
||||
const { ticker, position, orders, bars, timeframe, range, stockRecord } = useLoaderData() as LoaderData;
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
@@ -83,6 +106,8 @@ export default function StockDetail() {
|
||||
const [decision, setDecision] = useState<TradingDecision | null>(null);
|
||||
const [showAnalysts, setShowAnalysts] = useState(false);
|
||||
const [showDebate, setShowDebate] = useState(false);
|
||||
const [jobStatus, setJobStatus] = useState<any>(null);
|
||||
const [jobPolling, setJobPolling] = useState(false);
|
||||
|
||||
// Cache key for this ticker
|
||||
const cacheKey = `tradinggraph-${ticker}`;
|
||||
@@ -100,7 +125,32 @@ export default function StockDetail() {
|
||||
console.error("Failed to parse cached trading graph data:", e);
|
||||
}
|
||||
}
|
||||
}, [cacheKey]);
|
||||
|
||||
// If stock record contains a job id, start polling job status
|
||||
if (stockRecord?.lastJobId) {
|
||||
setJobPolling(true);
|
||||
let cancelled = false;
|
||||
const poll = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/jobs/${stockRecord.lastJobId}`);
|
||||
if (!res.ok) return;
|
||||
const j = await res.json();
|
||||
if (cancelled) return;
|
||||
setJobStatus(j);
|
||||
if (j.state === "completed" || j.state === "failed") {
|
||||
setJobPolling(false);
|
||||
cancelled = true;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to poll job status:", e);
|
||||
}
|
||||
setTimeout(poll, 1000);
|
||||
};
|
||||
poll();
|
||||
return () => { cancelled = true; };
|
||||
}
|
||||
}, [cacheKey, stockRecord]);
|
||||
|
||||
const updateParams = (newTimeframe: string, newRange: string) => {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
@@ -245,6 +295,16 @@ export default function StockDetail() {
|
||||
>
|
||||
{analysisLoading ? "Running Trading Graph..." : "Run Trading Graph Analysis"}
|
||||
</button>
|
||||
|
||||
{/* Job status link */}
|
||||
{stockRecord?.lastJobId && (
|
||||
<div className="mt-3 text-sm text-gray-600">
|
||||
Background job: <a href={`/api/jobs/${stockRecord.lastJobId}`} className="text-blue-600 hover:underline">{stockRecord.lastJobId}</a>
|
||||
{jobStatus && (
|
||||
<span className="ml-3">Status: <strong>{jobStatus.state}</strong></span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||
|
||||
Reference in New Issue
Block a user