Fix strategy timeline timestamp handling
This commit is contained in:
parent
5577de7b9e
commit
a22e8d7ee8
@ -31,6 +31,14 @@ type StrategyTimelineProps = {
|
|||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function parseTimestamp(value?: string | null) {
|
||||||
|
if (!value) return null;
|
||||||
|
const hasTimezone = /Z|[+-]\d{2}:?\d{2}$/.test(value);
|
||||||
|
const normalized = hasTimezone ? value : `${value}Z`;
|
||||||
|
const parsed = new Date(normalized);
|
||||||
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeLog(log: unknown): StrategyEvent {
|
function normalizeLog(log: unknown): StrategyEvent {
|
||||||
if (typeof log === "string") {
|
if (typeof log === "string") {
|
||||||
try {
|
try {
|
||||||
@ -49,25 +57,28 @@ function normalizeLog(log: unknown): StrategyEvent {
|
|||||||
return { message: "Unknown log entry" };
|
return { message: "Unknown log entry" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTimestamp(value?: string) {
|
function formatTimestamp(value?: string | null) {
|
||||||
if (!value) return "Unknown time";
|
if (!value) return "Unknown time";
|
||||||
const hasTimezone = /Z|[+-]\d{2}:?\d{2}$/.test(value);
|
const parsed = parseTimestamp(value);
|
||||||
const normalized = hasTimezone ? value : `${value}Z`;
|
if (!parsed) return value;
|
||||||
const parsed = new Date(normalized);
|
|
||||||
if (Number.isNaN(parsed.getTime())) return value;
|
|
||||||
return parsed.toLocaleString();
|
return parsed.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCompactTimestamp(value?: string | null) {
|
function formatCompactTimestamp(value?: string | null) {
|
||||||
if (!value) return null;
|
const parsed = parseTimestamp(value);
|
||||||
const parsed = new Date(value);
|
if (!parsed) return null;
|
||||||
if (Number.isNaN(parsed.getTime())) return null;
|
return parsed.toLocaleString([], {
|
||||||
return parsed.toLocaleTimeString([], {
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTimestampMs(value?: string | null) {
|
||||||
|
return parseTimestamp(value)?.getTime() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
function getRunId(entry: StrategyEvent) {
|
function getRunId(entry: StrategyEvent) {
|
||||||
return entry.run_id ?? "unknown";
|
return entry.run_id ?? "unknown";
|
||||||
}
|
}
|
||||||
@ -178,9 +189,7 @@ function CompactStrategySummary() {
|
|||||||
<div className={`rounded-lg border px-4 py-3 text-sm ${className}`}>
|
<div className={`rounded-lg border px-4 py-3 text-sm ${className}`}>
|
||||||
<div className="flex flex-col gap-1 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-1 md:flex-row md:items-center md:justify-between">
|
||||||
<span>{message}</span>
|
<span>{message}</span>
|
||||||
{updatedAt ? (
|
{updatedAt ? <span className="text-xs opacity-80">Updated {updatedAt}</span> : null}
|
||||||
<span className="text-xs opacity-80">Updated {updatedAt}</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -228,25 +237,34 @@ function VerboseStrategyTimeline() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const groupedRuns = useMemo<StrategyRun[]>(() => {
|
const groupedRuns = useMemo<StrategyRun[]>(() => {
|
||||||
const indexed = logs.map((entry, index) => ({ entry, index }));
|
const grouped = new Map<string, StrategyEvent[]>();
|
||||||
indexed.sort((a, b) => {
|
for (const entry of logs) {
|
||||||
const runCompare = getRunId(b.entry).localeCompare(getRunId(a.entry));
|
const runId = getRunId(entry);
|
||||||
if (runCompare !== 0) return runCompare;
|
const events = grouped.get(runId) ?? [];
|
||||||
const seqCompare = getSeq(a.entry) - getSeq(b.entry);
|
events.push(entry);
|
||||||
|
grouped.set(runId, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups = Array.from(grouped.entries()).map(([runId, events]) => ({
|
||||||
|
runId,
|
||||||
|
events: [...events].sort((a, b) => {
|
||||||
|
const seqCompare = getSeq(a) - getSeq(b);
|
||||||
|
if (seqCompare !== 0) return seqCompare;
|
||||||
|
return getTimestampMs(a.ts ?? a.timestamp) - getTimestampMs(b.ts ?? b.timestamp);
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
groups.sort((a, b) => {
|
||||||
|
const aLast = a.events[a.events.length - 1];
|
||||||
|
const bLast = b.events[b.events.length - 1];
|
||||||
|
const seqCompare = getSeq(bLast) - getSeq(aLast);
|
||||||
if (seqCompare !== 0) return seqCompare;
|
if (seqCompare !== 0) return seqCompare;
|
||||||
return a.index - b.index;
|
const timeCompare =
|
||||||
|
getTimestampMs(bLast.ts ?? bLast.timestamp) - getTimestampMs(aLast.ts ?? aLast.timestamp);
|
||||||
|
if (timeCompare !== 0) return timeCompare;
|
||||||
|
return b.runId.localeCompare(a.runId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const groups: StrategyRun[] = [];
|
|
||||||
for (const { entry } of indexed) {
|
|
||||||
const runId = getRunId(entry);
|
|
||||||
const last = groups[groups.length - 1];
|
|
||||||
if (!last || last.runId !== runId) {
|
|
||||||
groups.push({ runId, events: [entry] });
|
|
||||||
} else {
|
|
||||||
last.events.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return groups;
|
return groups;
|
||||||
}, [logs]);
|
}, [logs]);
|
||||||
|
|
||||||
@ -316,7 +334,7 @@ function VerboseStrategyTimeline() {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="text-xs text-green-300/80">
|
<div className="text-xs text-green-300/80">
|
||||||
{formatTimestamp(timestamp)} • {entry.event ?? "UNKNOWN"}
|
{formatTimestamp(timestamp)} - {entry.event ?? "UNKNOWN"}
|
||||||
</div>
|
</div>
|
||||||
{message && message !== entry.event ? (
|
{message && message !== entry.event ? (
|
||||||
<div className="text-sm text-green-200">{message}</div>
|
<div className="text-sm text-green-200">{message}</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user