Skip to content

Commit 75fa631

Browse files
committed
bugfixes-decision
1 parent 7a1f4cb commit 75fa631

2 files changed

Lines changed: 63 additions & 29 deletions

File tree

internal/history/store.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ func (s *Store) RecordRepeatedOffenderEvent(ctx context.Context, offender models
660660
window_days = excluded.window_days,
661661
last_seen_at = excluded.last_seen_at,
662662
last_notified_at = CASE
663-
WHEN excluded.last_notified_at = '' THEN repeated_offender_events.last_notified_at
663+
WHEN excluded.last_notified_at IS NULL THEN repeated_offender_events.last_notified_at
664664
ELSE excluded.last_notified_at
665665
END
666666
`, fp, offender.Value, offender.Scope, offender.HitCount, offender.WindowDays, lastSeen, lastSeen, lastNotify)

web/src/pages/DecisionAnalysis.tsx

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ export default function DecisionAnalysis() {
134134
const [searchQuery, setSearchQuery] = useState('')
135135
const [hideDuplicates, setHideDuplicates] = useState(false)
136136
const [hideExpired, setHideExpired] = useState(true)
137+
const [offenderPage, setOffenderPage] = useState(0)
138+
const OFFENDER_PAGE_SIZE = 20
137139
const { query, setQuery } = useSearch()
138140
const { lastEvent } = useSSE('/api/events/sse')
139141
const seenRealtimeEventsRef = useRef<Set<string>>(new Set())
@@ -176,6 +178,11 @@ export default function DecisionAnalysis() {
176178
refetchInterval: 60000,
177179
})
178180

181+
const allOffenders = repeatedOffendersData?.offenders ?? []
182+
const offenderTotalPages = Math.ceil(allOffenders.length / OFFENDER_PAGE_SIZE)
183+
const safePage = Math.min(offenderPage, Math.max(0, offenderTotalPages - 1))
184+
const pagedOffenders = allOffenders.slice(safePage * OFFENDER_PAGE_SIZE, (safePage + 1) * OFFENDER_PAGE_SIZE)
185+
179186
// Build a lookup map: alert_id -> AlertSource
180187
const alertSourceMap = useMemo(() => {
181188
const map = new Map<number, AlertSource>()
@@ -458,35 +465,62 @@ export default function DecisionAnalysis() {
458465
</CardDescription>
459466
</CardHeader>
460467
<CardContent>
461-
{repeatedOffendersData?.offenders && repeatedOffendersData.offenders.length > 0 ? (
462-
<div className="rounded-md border overflow-x-auto">
463-
<Table>
464-
<TableHeader>
465-
<TableRow>
466-
<TableHead>Value</TableHead>
467-
<TableHead>Scope</TableHead>
468-
<TableHead>Hits</TableHead>
469-
<TableHead>First Seen</TableHead>
470-
<TableHead>Last Seen</TableHead>
471-
<TableHead>Last Notified</TableHead>
472-
</TableRow>
473-
</TableHeader>
474-
<TableBody>
475-
{repeatedOffendersData.offenders.map((offender: RepeatedOffender) => (
476-
<TableRow key={`${offender.value}-${offender.scope}`}>
477-
<TableCell className="font-mono text-sm">{offender.value}</TableCell>
478-
<TableCell><Badge variant="outline">{offender.scope}</Badge></TableCell>
479-
<TableCell>{offender.hit_count}</TableCell>
480-
<TableCell><TimeDisplay date={offender.first_decision_at} /></TableCell>
481-
<TableCell><TimeDisplay date={offender.last_decision_at} /></TableCell>
482-
<TableCell>
483-
{offender.last_notified_at ? <TimeDisplay date={offender.last_notified_at} /> : 'Never'}
484-
</TableCell>
468+
{allOffenders.length > 0 ? (
469+
<>
470+
<div className="rounded-md border overflow-x-auto">
471+
<Table>
472+
<TableHeader>
473+
<TableRow>
474+
<TableHead>Value</TableHead>
475+
<TableHead>Scope</TableHead>
476+
<TableHead>Hits</TableHead>
477+
<TableHead>First Seen</TableHead>
478+
<TableHead>Last Seen</TableHead>
479+
<TableHead>Last Notified</TableHead>
485480
</TableRow>
486-
))}
487-
</TableBody>
488-
</Table>
489-
</div>
481+
</TableHeader>
482+
<TableBody>
483+
{pagedOffenders.map((offender: RepeatedOffender) => (
484+
<TableRow key={`${offender.value}-${offender.scope}`}>
485+
<TableCell className="font-mono text-sm">{offender.value}</TableCell>
486+
<TableCell><Badge variant="outline">{offender.scope}</Badge></TableCell>
487+
<TableCell>{offender.hit_count}</TableCell>
488+
<TableCell><TimeDisplay date={offender.first_decision_at} /></TableCell>
489+
<TableCell><TimeDisplay date={offender.last_decision_at} /></TableCell>
490+
<TableCell>
491+
{offender.last_notified_at ? <TimeDisplay date={offender.last_notified_at} /> : 'Never'}
492+
</TableCell>
493+
</TableRow>
494+
))}
495+
</TableBody>
496+
</Table>
497+
</div>
498+
{allOffenders.length > OFFENDER_PAGE_SIZE && (
499+
<div className="flex items-center justify-between px-2 pt-3">
500+
<span className="text-sm text-muted-foreground">
501+
Page {safePage + 1} of {offenderTotalPages} · {allOffenders.length} total
502+
</span>
503+
<div className="flex gap-2">
504+
<Button
505+
variant="outline"
506+
size="sm"
507+
disabled={safePage === 0}
508+
onClick={() => setOffenderPage(p => Math.max(0, p - 1))}
509+
>
510+
Previous
511+
</Button>
512+
<Button
513+
variant="outline"
514+
size="sm"
515+
disabled={safePage >= offenderTotalPages - 1}
516+
onClick={() => setOffenderPage(p => Math.min(offenderTotalPages - 1, p + 1))}
517+
>
518+
Next
519+
</Button>
520+
</div>
521+
</div>
522+
)}
523+
</>
490524
) : (
491525
<div className="text-sm text-muted-foreground">No repeated offenders detected in the last 30 days.</div>
492526
)}

0 commit comments

Comments
 (0)