Skip to content

Conversation

jon-bell
Copy link
Contributor

@jon-bell jon-bell commented Oct 7, 2025

Summary by CodeRabbit

  • New Features

    • Real-time, controller-driven workflow runs table; dynamic per-column multi-select filters; repository-aware external links for Run ID and Commit; one-click refresh and an empty “No workflow runs found” state.
  • Improvements

    • Clickable sortable headers with icons, simplified controls (controller-managed paging), computed status indicators, and concise timeline/time displays.
  • Performance

    • Updated metrics pipeline and schema to reduce DB load and support more efficient class-level statistics.
  • Bug Fixes

    • More reliable webhook processing with duplicate prevention, error reporting, and safer handling of non-main refs.

Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Warning

Rate limit exceeded

@jon-bell has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 38 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 7dc327f and 6c71957.

📒 Files selected for processing (4)
  • app/course/[course_id]/assignments/[assignment_id]/page.tsx (1 hunks)
  • supabase/functions/_shared/SupabaseTypes.d.ts (2 hunks)
  • supabase/migrations/20251006232741_reduce-db-ram-usage.sql (1 hunks)
  • utils/supabase/SupabaseTypes.d.ts (2 hunks)

Walkthrough

Replaces a materialized-view summary with a new realtime public.workflow_runs table and trigger-based maintenance; migrates webhook dedup/status tracking to Redis; updates Supabase types and RPCs to use workflow_runs; refactors the frontend workflow-runs page to use a TableController-backed data layer with unified filters, links, and live refresh.

Changes

Cohort / File(s) Summary
Frontend: Workflow Runs Table Refactor
app/course/[course_id]/manage/workflow-runs/runs/page.tsx
Replace manual fetching with a TableController-backed table; add repository_name-aware Run ID/Commit links (GitHub external links when present); move to CreatableSelect unified filtering with dynamic per-column options; add refresh button (controller.refetchAll), sortable headers with icons, empty/no-results state, and remove legacy paging/spinner.
Supabase Types (Functions Shared)
supabase/functions/_shared/SupabaseTypes.d.ts
Remove webhook_process_status and workflow_events_summary types; add workflow_runs table types (Row/Insert/Update/Relationships); update get_workflow_events_summary_for_class return type to structured workflow_runs[]; drop refresh_workflow_events_summary.
Supabase Types (Client Utils)
utils/supabase/SupabaseTypes.d.ts
Mirror shared type changes: add workflow_runs, remove webhook_process_status and workflow_events_summary, update return types for relevant RPCs, remove refresh function.
Webhook Processing (Edge Function)
supabase/functions/github-repo-webhook/index.ts
Add Redis-backed webhook status tracking (per-delivery TTL, attempt_count, last_attempt_at, last_error); gate duplicates via Redis; mark completion/errors in Redis; add Redis error handling and Sentry logging; remove direct DB writes to webhook_process_status; add branch/ref guards and extra tracing logs.
Database Migration & Functions
supabase/migrations/20251006232741_reduce-db-ram-usage.sql
Create public.workflow_runs table with unique constraint, FKs, indexes, RLS policies and grants; add maintain_workflow_runs() trigger to upsert run state; replace materialized-view flows with RPCs reading workflow_runs; add get_workflow_statistics, get_all_class_metrics, check_assignment_deadlines_passed; drop refresh_workflow_events_summary and unused indexes; add supporting indexes (including submission_comments updated_at).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant GH as GitHub Webhook
  participant Edge as Webhook Function
  participant Redis as Redis (TTL state)
  participant PG as Postgres

  GH->>Edge: Deliver event (delivery id, payload)
  Edge->>Redis: GET/SETNX status key (TTL 3h)
  alt Duplicate or Completed
    Redis-->>Edge: status indicates processed
    Edge-->>GH: 200 Early exit
  else First delivery or retry
    Edge->>PG: Persist event / enqueue processing
    PG-->>Edge: OK
    Edge->>Redis: UPDATE status (completed/attempts/errors)
    Edge-->>GH: 200 OK
  end
  note right of PG: Trigger maintain_workflow_runs upserts per-run state
Loading
sequenceDiagram
  autonumber
  participant UI as Runs Page
  participant TC as TableController
  participant RPC as Supabase RPC
  participant DB as workflow_runs

  UI->>TC: Initialize (class_id, sort, pageSize)
  TC->>RPC: call get_workflow_events_summary_for_class(class_id)
  RPC->>DB: SELECT FROM workflow_runs (RLS)
  DB-->>RPC: Rows
  RPC-->>TC: Rows + computed filter options
  TC-->>UI: Render rows, filters, links
  UI->>TC: Click Refresh
  TC->>RPC: Refetch
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, backend, database, frontend

Poem

Rows flow in streams and triggers hum,
Redis keeps count while jobs become.
Tables refresh, filters take flight,
Metrics sing clear in morning light.
Code marched forward — quiet, bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title highlights the removal of memory-intensive maintenance functions, which is a genuine aspect of the changeset, but it omits the significant frontend table refactoring and webhook handling updates included in this PR, making it only partially representative of the full scope.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d16aa6 and db8d68d.

📒 Files selected for processing (5)
  • app/course/[course_id]/manage/workflow-runs/runs/page.tsx (13 hunks)
  • supabase/functions/_shared/SupabaseTypes.d.ts (2 hunks)
  • supabase/functions/github-repo-webhook/index.ts (5 hunks)
  • supabase/migrations/20251006232741_reduce-db-ram-usage.sql (1 hunks)
  • utils/supabase/SupabaseTypes.d.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
supabase/functions/github-repo-webhook/index.ts (1)
supabase/functions/_shared/Redis.ts (1)
  • Redis (12-488)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (4)
utils/supabase/client.ts (1)
  • createClient (6-17)
hooks/useCourseController.tsx (2)
  • useCourseController (1387-1393)
  • classRealTimeController (406-411)
lib/TableController.ts (2)
  • TableController (461-2222)
  • rows (2215-2217)
hooks/useTableControllerTable.tsx (1)
  • useTableControllerTable (69-175)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint (22.x)
  • GitHub Check: lint (22.x)
  • GitHub Check: deploy
🔇 Additional comments (3)
supabase/functions/_shared/SupabaseTypes.d.ts (2)

7715-7842: Workflow run table typings look consistent

Schema, optional fields, and FK mappings all line up with the new migration. 👍


9369-9390: Updated summary return type matches the new table

The function signature now mirrors the workflow_runs columns and keeps nullability intact.

utils/supabase/SupabaseTypes.d.ts (1)

7715-7843: Schema typings look consistent
The workflow_runs table definition and the updated get_workflow_events_summary_for_class return type line up with the migration plan—nullability, required Insert fields, and relationship metadata all look good.

Also applies to: 9368-9390

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (1)

332-356: Don’t hide zero durations

0s currently renders as “-” due to falsy checks. Use null/undefined checks.

-          const seconds = getValue() as number;
-          if (!seconds) return <Text fontSize="sm">-</Text>;
+          const seconds = getValue() as number | null | undefined;
+          if (seconds === null || seconds === undefined) return <Text fontSize="sm">-</Text>;
@@
-                {Math.round(seconds)}s
+                {Math.round(seconds)}s
-          const seconds = getValue() as number;
-          if (!seconds) return <Text fontSize="sm">-</Text>;
+          const seconds = getValue() as number | null | undefined;
+          if (seconds === null || seconds === undefined) return <Text fontSize="sm">-</Text>;
@@
-                {Math.round(seconds)}s
+                {Math.round(seconds)}s

Also applies to: 365-389

♻️ Duplicate comments (1)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (1)

433-475: Resolved: filter options recompute when rows change

The memo now depends on workflowRuns, fixing the stale-options issue noted previously.

🧹 Nitpick comments (6)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (6)

480-489: Use IconButton’s icon prop (children are ignored/not styled consistently)

Switch to the icon prop for correct sizing/accessibility.

-        <IconButton
-          aria-label="Refresh workflow runs"
-          size="sm"
-          variant="outline"
-          onClick={async () => {
-            await controller?.refetchAll();
-          }}
-        >
-          <LuRefreshCw />
-        </IconButton>
+        <IconButton
+          aria-label="Refresh workflow runs"
+          size="sm"
+          variant="outline"
+          icon={<LuRefreshCw />}
+          onClick={async () => {
+            await controller?.refetchAll();
+          }}
+        />

Based on learnings


518-532: Chakra Icon prop: use boxSize, not size

Replace size="md" with boxSize to avoid invalid DOM attrs and ensure consistent sizing.

-                                asc: (
-                                  <Icon size="md">
+                                asc: (
+                                  <Icon boxSize={4}>
                                     <FaSortUp />
                                   </Icon>
                                 ),
-                                desc: (
-                                  <Icon size="md">
+                                desc: (
+                                  <Icon boxSize={4}>
                                     <FaSortDown />
                                   </Icon>
                                 )
                               }[header.column.getIsSorted() as string] ?? (
-                                <Icon size="md">
+                                <Icon boxSize={4}>
                                   <FaSort />
                                 </Icon>
                               )}

Based on learnings


98-101: Avoid masking 0 for numeric cells

Use nullish coalescing instead of logical OR.

-        <Text fontSize="sm">{(getValue() as number) || "-"}</Text>
+        <Text fontSize="sm">{(getValue() as number | null | undefined) ?? "-"}</Text>
-        <Text fontSize="sm">{(getValue() as number) || "-"}</Text>
+        <Text fontSize="sm">{(getValue() as number | null | undefined) ?? "-"}</Text>

Also applies to: 315-317


406-412: Scope realtime updates to this class_id

Pass a realtimeFilter so the controller ignores events for other classes; reduces noise and memory.

     return new TableController({
       query: query,
       client: supabase,
       table: "workflow_runs",
-      classRealTimeController
+      classRealTimeController,
+      realtimeFilter: { class_id: Number(course_id) }
     });

273-283: Tighten status filtering to exact match

Substring match can yield false positives; use equality against option values.

-          const filterArray = Array.isArray(filterValue) ? filterValue : [filterValue];
-          return filterArray.some((filter: string) => status.includes(filter.toLowerCase()));
+          const filterArray = Array.isArray(filterValue) ? filterValue : [filterValue];
+          return filterArray.some((filter: string) => status === filter.toLowerCase());

542-549: Preserve option labels in the select chips

Map current filter values to the actual option objects so labels (e.g., “#123”) remain consistent.

-                                value={
-                                  Array.isArray(header.column.getFilterValue())
-                                    ? (header.column.getFilterValue() as string[]).map((v) => ({
-                                        label: v,
-                                        value: v
-                                      }))
-                                    : []
-                                }
+                                value={
+                                  Array.isArray(header.column.getFilterValue())
+                                    ? options.filter((opt) =>
+                                        (header.column.getFilterValue() as string[]).includes(opt.value)
+                                      )
+                                    : []
+                                }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db8d68d and 10b336f.

📒 Files selected for processing (2)
  • app/course/[course_id]/manage/workflow-runs/runs/page.tsx (13 hunks)
  • supabase/migrations/20251006232741_reduce-db-ram-usage.sql (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (5)
components/ui/link.tsx (1)
  • Link (4-42)
utils/supabase/client.ts (1)
  • createClient (6-17)
hooks/useCourseController.tsx (2)
  • useCourseController (1387-1393)
  • classRealTimeController (406-411)
lib/TableController.ts (1)
  • TableController (461-2222)
hooks/useTableControllerTable.tsx (1)
  • useTableControllerTable (69-175)
🪛 GitHub Actions: Lint
app/course/[course_id]/manage/workflow-runs/runs/page.tsx

[error] 1-1: Code style issues found by Prettier. Run 'prettier --write' to fix formatting.

⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (1)

1-1: Run Prettier to fix formatting and unblock CI.

The Prettier formatting issue was flagged in a previous review. Please run the following command to fix formatting:

npx prettier --write "app/course/[course_id]/manage/workflow-runs/runs/page.tsx"
🧹 Nitpick comments (2)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (2)

56-84: Consider extracting GitHub link rendering into a helper function.

The Run ID and Commit columns have similar link-rendering logic. If you anticipate adding more GitHub-linked columns, extracting this into a reusable helper function would reduce duplication.

Example refactor:

const renderGitHubLink = (
  text: string,
  url: string,
  repoName: string | null | undefined
) => {
  if (!repoName) {
    return (
      <Text fontFamily="mono" fontSize={text.startsWith('#') ? 'sm' : 'xs'} color={text.startsWith('#') ? undefined : 'fg.muted'}>
        {text}
      </Text>
    );
  }
  return (
    <Link
      href={url}
      target="_blank"
      rel="noopener noreferrer"
      color="blue.600"
      _hover={{ color: "blue.700", textDecoration: "underline" }}
    >
      <HStack gap={1}>
        <Text fontFamily="mono" fontSize={text.startsWith('#') ? 'sm' : 'xs'}>
          {text}
        </Text>
        <Icon boxSize={3}>
          <LuExternalLink />
        </Icon>
      </HStack>
    </Link>
  );
};

Then use:

cell: ({ getValue, row }) => {
  const runId = getValue() as number;
  const repoName = row.original.repository_name;
  return renderGitHubLink(
    `#${runId}`,
    `https://github.com/${repoName}/actions/runs/${runId}`,
    repoName
  );
}

Also applies to: 166-194


395-395: Remove unnecessary memoization of createClient().

The useMemo around createClient() doesn't provide any benefit since createClient() returns a new Supabase client instance each time. The memoization prevents the reference from changing, but since the underlying client is new anyway, this doesn't optimize anything meaningful.

Apply this diff:

-  const supabase = useMemo(() => createClient(), []);
+  const supabase = createClient();

Or, if you intend to reuse the same client instance across re-renders (which is the correct approach), move the client creation outside the component or use a singleton pattern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10b336f and 7dc327f.

📒 Files selected for processing (2)
  • app/course/[course_id]/manage/workflow-runs/runs/page.tsx (13 hunks)
  • supabase/migrations/20251006232741_reduce-db-ram-usage.sql (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (4)
utils/supabase/client.ts (1)
  • createClient (6-17)
hooks/useCourseController.tsx (2)
  • useCourseController (1387-1393)
  • classRealTimeController (406-411)
lib/TableController.ts (1)
  • TableController (461-2222)
hooks/useTableControllerTable.tsx (1)
  • useTableControllerTable (69-175)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy
🔇 Additional comments (7)
app/course/[course_id]/manage/workflow-runs/runs/page.tsx (2)

433-474: LGTM! The filter option cache is now correctly reactive.

The columnUniqueValues memo now depends on workflowRuns, which ensures filter options are recomputed when data arrives. This correctly addresses the previous issue where the memo captured an empty row model on first render and never updated.


478-490: LGTM! Refresh button, filters, and table rendering look solid.

The refresh button correctly triggers controller?.refetchAll() with optional chaining for safety. The dynamic filter UI using CreatableSelect is properly configured with:

  • Multi-select enabled
  • Dynamic options from columnUniqueValues
  • Correct value transformation and change handlers
  • Appropriate styling

The header sorting indicators and row rendering are implemented correctly.

Also applies to: 505-577, 580-588

supabase/migrations/20251006232741_reduce-db-ram-usage.sql (5)

8-137: LGTM! Table schema, constraints, and RLS policies are well-designed.

The workflow_runs table schema is comprehensive with:

  • Proper unique constraint on (workflow_run_id, run_attempt, class_id)
  • Foreign keys with appropriate cascade behavior
  • Indexes optimized for common query patterns
  • RLS policies that correctly restrict access to instructors and service_role

The backfill from the materialized view preserves historical data safely.


139-322: LGTM! The trigger function correctly handles out-of-order webhook delivery.

The maintain_workflow_runs() function properly addresses both past review comments:

  1. Requested event handling (lines 185-208): Uses ON CONFLICT DO UPDATE with COALESCE logic to backfill requested_at and metadata fields when later events arrive first. This prevents data loss from out-of-order webhooks.

  2. In-progress event handling (lines 251-260): Correctly backfills run_time_seconds when in_progress arrives after completed, ensuring duration metrics are preserved.

All three event handlers (requested, in_progress, completed) use consistent COALESCE-based backfilling for metadata fields and recalculate timing metrics when both required timestamps become available. The function is well-optimized with early exits for irrelevant events.


323-620: LGTM! Functions correctly migrated to use workflow_runs table.

All public functions have been properly updated:

  • get_workflow_events_summary_for_class now returns SETOF workflow_runs with efficient ordering and LIMIT
  • get_workflow_statistics correctly calculates metrics from workflow_runs with proper authorization checks
  • get_all_class_metrics aggregates across all classes using the new table
  • Obsolete refresh_workflow_events_summary() function and cron job are safely removed with defensive error handling

The migration from materialized view to trigger-maintained table is complete and correct.


621-664: LGTM! Index cleanup achieves significant RAM reduction.

The index management correctly:

  • Drops 12 unused workflow_events indexes (~210 MB recovery)
  • Drops unused grader_result_output and submissions indexes (~60 MB recovery)
  • Adds targeted indexes to support the optimized check_assignment_deadlines_passed function
  • Total RAM recovery: ~270 MB, directly addressing the PR objective

The new indexes (idx_review_assignments_assignment_assignee_exists, idx_submissions_assignment_profile_group_active) are appropriately scoped to support specific query patterns.


666-808: LGTM! Highly optimized deadline processing function.

The check_assignment_deadlines_passed() function is well-optimized with:

  • MATERIALIZED CTEs for better query planning and performance
  • Time-based pre-filtering (30-day lookback) to dramatically reduce cross-join size
  • Early Anti Join on review_assignments before expensive submission lookups
  • Separated processing for submission_reviews and review_assignments creation
  • Efficient index usage via the newly added composite indexes

The optimization strategy is clearly documented in the comment block, and the function correctly handles both individual and group submissions. The addition of idx_submission_comments_updated_at at line 808 is appropriate for other query patterns.

coderabbitai[bot]
coderabbitai bot previously approved these changes Oct 7, 2025
@jon-bell jon-bell merged commit 77c4075 into staging Oct 7, 2025
5 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant