Skip to content

Commit 10b336f

Browse files
committed
nits
1 parent db8d68d commit 10b336f

File tree

2 files changed

+132
-18
lines changed

2 files changed

+132
-18
lines changed

app/course/[course_id]/manage/workflow-runs/runs/page.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ function WorkflowRunTable() {
413413

414414
const {
415415
getHeaderGroups,
416-
getRowModel,
417416
getCoreRowModel,
418417
data,
419418
tableController: controller
@@ -429,9 +428,10 @@ function WorkflowRunTable() {
429428
}
430429
});
431430

431+
const workflowRuns = getCoreRowModel().rows;
432+
432433
// Compute unique values for filter options from ALL rows (before filtering)
433434
const columnUniqueValues = useMemo(() => {
434-
const rows = getCoreRowModel().rows;
435435
const uniqueValuesMap: Record<string, SelectOption[]> = {};
436436

437437
columns.forEach((column) => {
@@ -440,7 +440,7 @@ function WorkflowRunTable() {
440440
if (column.id === "status") {
441441
// Status is computed from timestamps
442442
const statuses = new Set<string>();
443-
rows.forEach((row) => {
443+
workflowRuns.forEach((row) => {
444444
const { requested_at, in_progress_at, completed_at } = row.original;
445445
if (completed_at) statuses.add("completed");
446446
else if (in_progress_at) statuses.add("in progress");
@@ -455,7 +455,7 @@ function WorkflowRunTable() {
455455
}
456456

457457
const uniqueValues = new Set<string>();
458-
rows.forEach((row) => {
458+
workflowRuns.forEach((row) => {
459459
const value = row.getValue(column.id as string);
460460
if (value !== null && value !== undefined) {
461461
uniqueValues.add(String(value));
@@ -471,9 +471,7 @@ function WorkflowRunTable() {
471471
});
472472

473473
return uniqueValuesMap;
474-
}, [columns, getCoreRowModel]);
475-
476-
const workflowRuns = getRowModel().rows;
474+
}, [columns, workflowRuns]);
477475

478476
return (
479477
<Box>

supabase/migrations/20251006232741_reduce-db-ram-usage.sql

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,65 @@ CREATE INDEX idx_workflow_runs_profile_id ON public.workflow_runs USING btree (p
5353
-- Index for efficient upsert lookups (supports the UNIQUE constraint)
5454
CREATE INDEX idx_workflow_runs_unique_lookup ON public.workflow_runs USING btree (workflow_run_id, run_attempt, class_id);
5555

56+
-- Backfill workflow_runs table with existing data from the materialized view
57+
-- This preserves historical data before we drop the view
58+
INSERT INTO public.workflow_runs (
59+
workflow_run_id,
60+
class_id,
61+
repository_name,
62+
workflow_name,
63+
workflow_path,
64+
head_sha,
65+
head_branch,
66+
run_number,
67+
run_attempt,
68+
actor_login,
69+
triggering_actor_login,
70+
assignment_id,
71+
profile_id,
72+
requested_at,
73+
in_progress_at,
74+
completed_at,
75+
conclusion,
76+
queue_time_seconds,
77+
run_time_seconds,
78+
created_at,
79+
updated_at
80+
)
81+
SELECT
82+
workflow_run_id,
83+
class_id,
84+
repository_name,
85+
workflow_name,
86+
workflow_path,
87+
head_sha,
88+
head_branch,
89+
run_number,
90+
run_attempt,
91+
actor_login,
92+
triggering_actor_login,
93+
assignment_id,
94+
profile_id,
95+
requested_at,
96+
in_progress_at,
97+
completed_at,
98+
conclusion,
99+
queue_time_seconds,
100+
run_time_seconds,
101+
NOW() as created_at,
102+
NOW() as updated_at
103+
FROM public.workflow_events_summary
104+
ON CONFLICT ON CONSTRAINT workflow_runs_unique_run DO NOTHING;
105+
106+
-- Log the backfill results
107+
DO $$
108+
DECLARE
109+
backfilled_count bigint;
110+
BEGIN
111+
SELECT COUNT(*) INTO backfilled_count FROM public.workflow_runs;
112+
RAISE NOTICE 'Backfilled % workflow runs from materialized view', backfilled_count;
113+
END $$;
114+
56115
-- Enable RLS
57116
ALTER TABLE public.workflow_runs ENABLE ROW LEVEL SECURITY;
58117

@@ -105,9 +164,11 @@ BEGIN
105164

106165
-- Event-specific upsert: only update fields relevant to each event type
107166
-- This avoids unnecessary COALESCE operations and reduces UPDATE overhead
167+
-- CRITICAL: Handles out-of-order webhook delivery (retries, race conditions)
108168
CASE NEW.event_type
109169
WHEN 'requested' THEN
110170
-- First event: INSERT with metadata, only requested_at timestamp
171+
-- If row exists (in_progress/completed arrived first), backfill requested_at and metadata
111172
INSERT INTO public.workflow_runs (
112173
workflow_run_id, class_id, run_attempt, repository_name,
113174
workflow_name, workflow_path, head_sha, head_branch, run_number,
@@ -121,10 +182,35 @@ BEGIN
121182
NEW.updated_at, NULL, NULL, NULL,
122183
NULL, NULL, NOW(), NOW()
123184
)
124-
ON CONFLICT ON CONSTRAINT workflow_runs_unique_run DO NOTHING;
185+
ON CONFLICT ON CONSTRAINT workflow_runs_unique_run
186+
DO UPDATE SET
187+
-- Backfill requested_at if it's NULL (out-of-order arrival)
188+
requested_at = COALESCE(workflow_runs.requested_at, EXCLUDED.requested_at),
189+
-- Backfill metadata fields if NULL (they may be missing if later events arrived first)
190+
repository_name = COALESCE(workflow_runs.repository_name, EXCLUDED.repository_name),
191+
workflow_name = COALESCE(workflow_runs.workflow_name, EXCLUDED.workflow_name),
192+
workflow_path = COALESCE(workflow_runs.workflow_path, EXCLUDED.workflow_path),
193+
head_sha = COALESCE(workflow_runs.head_sha, EXCLUDED.head_sha),
194+
head_branch = COALESCE(workflow_runs.head_branch, EXCLUDED.head_branch),
195+
run_number = COALESCE(workflow_runs.run_number, EXCLUDED.run_number),
196+
actor_login = COALESCE(workflow_runs.actor_login, EXCLUDED.actor_login),
197+
triggering_actor_login = COALESCE(workflow_runs.triggering_actor_login, EXCLUDED.triggering_actor_login),
198+
assignment_id = COALESCE(workflow_runs.assignment_id, EXCLUDED.assignment_id),
199+
profile_id = COALESCE(workflow_runs.profile_id, EXCLUDED.profile_id),
200+
-- Recalculate queue_time if we now have both timestamps and it was NULL
201+
queue_time_seconds = CASE
202+
WHEN workflow_runs.queue_time_seconds IS NULL
203+
AND workflow_runs.in_progress_at IS NOT NULL
204+
AND EXCLUDED.requested_at IS NOT NULL
205+
THEN EXTRACT(EPOCH FROM (workflow_runs.in_progress_at - EXCLUDED.requested_at))
206+
ELSE workflow_runs.queue_time_seconds
207+
END,
208+
updated_at = NOW();
125209

126210
WHEN 'in_progress' THEN
127-
-- Second event: only update in_progress_at and calculate queue time
211+
-- Second event: update in_progress_at and calculate queue time
212+
-- If row exists (requested/completed arrived first), backfill in_progress_at
213+
-- If row doesn't exist yet (requested not received), create with available data
128214
INSERT INTO public.workflow_runs (
129215
workflow_run_id, class_id, run_attempt, repository_name,
130216
workflow_name, workflow_path, head_sha, head_branch, run_number,
@@ -140,17 +226,33 @@ BEGIN
140226
)
141227
ON CONFLICT ON CONSTRAINT workflow_runs_unique_run
142228
DO UPDATE SET
143-
in_progress_at = EXCLUDED.in_progress_at,
144-
-- Calculate queue time if we now have both timestamps
229+
-- Always update in_progress_at (may arrive late or be a retry)
230+
in_progress_at = COALESCE(workflow_runs.in_progress_at, EXCLUDED.in_progress_at),
231+
-- Backfill metadata fields if NULL (requested event may not have arrived yet)
232+
repository_name = COALESCE(workflow_runs.repository_name, EXCLUDED.repository_name),
233+
workflow_name = COALESCE(workflow_runs.workflow_name, EXCLUDED.workflow_name),
234+
workflow_path = COALESCE(workflow_runs.workflow_path, EXCLUDED.workflow_path),
235+
head_sha = COALESCE(workflow_runs.head_sha, EXCLUDED.head_sha),
236+
head_branch = COALESCE(workflow_runs.head_branch, EXCLUDED.head_branch),
237+
run_number = COALESCE(workflow_runs.run_number, EXCLUDED.run_number),
238+
actor_login = COALESCE(workflow_runs.actor_login, EXCLUDED.actor_login),
239+
triggering_actor_login = COALESCE(workflow_runs.triggering_actor_login, EXCLUDED.triggering_actor_login),
240+
assignment_id = COALESCE(workflow_runs.assignment_id, EXCLUDED.assignment_id),
241+
profile_id = COALESCE(workflow_runs.profile_id, EXCLUDED.profile_id),
242+
-- Calculate queue time if we now have both timestamps and it was NULL
145243
queue_time_seconds = CASE
146-
WHEN workflow_runs.requested_at IS NOT NULL AND EXCLUDED.in_progress_at IS NOT NULL
244+
WHEN workflow_runs.queue_time_seconds IS NULL
245+
AND workflow_runs.requested_at IS NOT NULL
246+
AND EXCLUDED.in_progress_at IS NOT NULL
147247
THEN EXTRACT(EPOCH FROM (EXCLUDED.in_progress_at - workflow_runs.requested_at))
148248
ELSE workflow_runs.queue_time_seconds
149249
END,
150250
updated_at = NOW();
151251

152252
WHEN 'completed' THEN
153-
-- Third event: only update completed_at, conclusion, and calculate run time
253+
-- Third event: update completed_at, conclusion, and calculate run time
254+
-- If row exists (requested/in_progress arrived first), backfill completed_at
255+
-- If row doesn't exist yet, create with available data
154256
INSERT INTO public.workflow_runs (
155257
workflow_run_id, class_id, run_attempt, repository_name,
156258
workflow_name, workflow_path, head_sha, head_branch, run_number,
@@ -166,11 +268,25 @@ BEGIN
166268
)
167269
ON CONFLICT ON CONSTRAINT workflow_runs_unique_run
168270
DO UPDATE SET
169-
completed_at = EXCLUDED.completed_at,
170-
conclusion = EXCLUDED.conclusion,
171-
-- Calculate run time if we now have both timestamps
271+
-- Always update completed_at and conclusion (may arrive late or be a retry)
272+
completed_at = COALESCE(workflow_runs.completed_at, EXCLUDED.completed_at),
273+
conclusion = COALESCE(workflow_runs.conclusion, EXCLUDED.conclusion),
274+
-- Backfill metadata fields if NULL (earlier events may not have arrived yet)
275+
repository_name = COALESCE(workflow_runs.repository_name, EXCLUDED.repository_name),
276+
workflow_name = COALESCE(workflow_runs.workflow_name, EXCLUDED.workflow_name),
277+
workflow_path = COALESCE(workflow_runs.workflow_path, EXCLUDED.workflow_path),
278+
head_sha = COALESCE(workflow_runs.head_sha, EXCLUDED.head_sha),
279+
head_branch = COALESCE(workflow_runs.head_branch, EXCLUDED.head_branch),
280+
run_number = COALESCE(workflow_runs.run_number, EXCLUDED.run_number),
281+
actor_login = COALESCE(workflow_runs.actor_login, EXCLUDED.actor_login),
282+
triggering_actor_login = COALESCE(workflow_runs.triggering_actor_login, EXCLUDED.triggering_actor_login),
283+
assignment_id = COALESCE(workflow_runs.assignment_id, EXCLUDED.assignment_id),
284+
profile_id = COALESCE(workflow_runs.profile_id, EXCLUDED.profile_id),
285+
-- Calculate run time if we now have both timestamps and it was NULL
172286
run_time_seconds = CASE
173-
WHEN workflow_runs.in_progress_at IS NOT NULL AND EXCLUDED.completed_at IS NOT NULL
287+
WHEN workflow_runs.run_time_seconds IS NULL
288+
AND workflow_runs.in_progress_at IS NOT NULL
289+
AND EXCLUDED.completed_at IS NOT NULL
174290
THEN EXTRACT(EPOCH FROM (EXCLUDED.completed_at - workflow_runs.in_progress_at))
175291
ELSE workflow_runs.run_time_seconds
176292
END,
@@ -182,7 +298,7 @@ END;
182298
$$;
183299

184300
COMMENT ON FUNCTION public.maintain_workflow_runs() IS
185-
'Automatically maintains workflow_runs table by aggregating workflow_events. Replaces the expensive materialized view refresh with real-time updates.';
301+
'Automatically maintains workflow_runs table by aggregating workflow_events. Replaces the expensive materialized view refresh with real-time updates. Handles out-of-order webhook delivery by backfilling missing fields and recalculating metrics when late events arrive.';
186302

187303
-- Create trigger on workflow_events
188304
CREATE TRIGGER trigger_maintain_workflow_runs

0 commit comments

Comments
 (0)