Skip to content

perf(supabase): Eliminate N+1 queries and optimize fetch payloads in SupabaseService #149#218

Open
VanshKaushal wants to merge 4 commits intoAOSSIE-Org:mainfrom
VanshKaushal:fix/n-plus-1-queries
Open

perf(supabase): Eliminate N+1 queries and optimize fetch payloads in SupabaseService #149#218
VanshKaushal wants to merge 4 commits intoAOSSIE-Org:mainfrom
VanshKaushal:fix/n-plus-1-queries

Conversation

@VanshKaushal
Copy link
Copy Markdown

@VanshKaushal VanshKaushal commented Mar 13, 2026

This PR improves the performance of multiple data-fetching methods in SupabaseService by removing N+1 query patterns and optimizing database payload size.

Previously, methods such as _doGetTasks(), getTaskDetails(), getTicketDetails(), getMeetings(), and getMeetingDetails() fetched base records and then executed additional asynchronous queries inside loops to retrieve related user information (creator, assignee, comment author) using _getUserInfo().

This resulted in multiple database round-trips per screen load and degraded performance as the number of records increased.

✅ What’s Changed

  • Replaced sequential _getUserInfo() calls with Supabase PostgREST relational joins to fetch related user data in a single query.

  • Removed N+1 query patterns across task, ticket, and meeting data-fetching methods.

  • Replaced all SELECT * queries with explicit column selection to reduce response payload size.

  • Ensured that existing filters (team_id, status, priority, assignments, etc.) continue to work as expected.

  • Verified that the returned nested JSON structure remains compatible with existing UI models, requiring no downstream changes.

⚡ Performance Impact

  • Reduces database round-trips from O(n) per screen load to O(1).

  • Improves data loading performance by fetching relational data (creator, assignee, comments, comment authors) in a single request.

  • Decreases network bandwidth usage due to optimized column selection.

🛠 Example Improvement

### Before (N+1 Query Pattern):

_final tasks = await _client.from('tasks').select('*');

for (var task in tasks) {
final creator = await getUserInfo(task['created_by']);
}

After (Relational Join):

_final tasks = await client.from('tasks').select('''
id, title, status, created_at,
creator:users!tasks_created_by_fkey(id, full_name, role)
''');

🧪 Validation

  • Tested with larger datasets to confirm reduced database calls.

  • Confirmed relational joins return correct nested user data.

  • Verified UI rendering remains unaffected.

📎 Notes
This change improves scalability and aligns the data layer with recommended Supabase query practices by avoiding unnecessary sequential requests.

Summary by CodeRabbit

  • New Features

    • GitHub integration: tickets can automatically create labeled GitHub issues and store issue links on the ticket.
  • Performance

    • Faster loading for tasks, tickets, and meetings by fetching related users and comments in consolidated queries.
    • Detail views return complete, UI-aligned data (including ordered comments) without per-record lookups.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 13, 2026

Walkthrough

Replaces per-record user lookups with relational joins for tasks, tickets, and meetings; consolidates comment retrieval and ordering via joins. Adds GitHub integration: a Deno edge function creates GitHub issues after ticket insertion and the ticket is updated with issue metadata. Migration adds GitHub fields to tickets.

Changes

Cohort / File(s) Summary
Supabase service (query refactor & GitHub flow)
lib/services/supabase_service.dart
Replaced N+1 per-record _getUserInfo calls with relational joins to fetch creators/assignees and comments in a single query; adjusted shaping of returned task/ticket/meeting objects; invokes cloud function to create GitHub issue after ticket insert and updates ticket with returned issue metadata.
Cloud function: create GitHub issue
supabase/functions/create-github-issue/index.ts
New Deno edge function that validates input and env vars, maps category/priority to labels, posts to GitHub Issues API, returns { success, issueNumber, issueUrl }, and includes CORS and error handling.
Database migration
supabase/migrations/20260309152700_add_github_issue_to_tickets.sql
Adds github_issue_number (TEXT) and github_issue_url (TEXT) to tickets and creates an index on github_issue_number.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant App as Supabase Service
    participant DB as Supabase DB
    participant CloudFunc as create-github-issue
    participant GitHub as GitHub API

    Client->>App: createTicket(payload)
    App->>DB: INSERT ticket
    DB-->>App: ticketId

    App->>CloudFunc: POST /create-github-issue (title, desc, category, priority, ticketId)

    rect rgba(100, 150, 255, 0.5)
    CloudFunc->>CloudFunc: validate input & env
    CloudFunc->>CloudFunc: format title/body & labels
    end

    CloudFunc->>GitHub: POST /repos/.../issues (title, body, labels)
    GitHub-->>CloudFunc: issueNumber, issueUrl
    CloudFunc-->>App: {success, issueNumber, issueUrl}

    rect rgba(100, 200, 100, 0.5)
    App->>DB: UPDATE ticket SET github_issue_number, github_issue_url
    DB-->>App: updated ticket
    end

    App-->>Client: ticket + github info
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Dart/Flutter

"I'm a rabbit with hops and code so spry,
Joins replace queries that made us sigh.
Tickets now spawn issues up above,
Comments ordered, creators in one shove.
🐇✨"

🚥 Pre-merge checks | ✅ 2
✅ 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 accurately describes the main objective of the PR: eliminating N+1 queries and optimizing fetch payloads in SupabaseService, which matches the primary changes across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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
Copy Markdown
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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/services/supabase_service.dart`:
- Around line 1754-1790: When the call to
_client.functions.invoke('create-github-issue', ...) returns a non-200
functionsResponse, log the response body along with the status to aid debugging:
update the else branch that currently calls debugPrint('GitHub Edge Function
returned status ${functionsResponse.status}') to include functionsResponse.data
(or the raw body) and any error fields; also extend the catch block
debugPrint('Failed to sync ticket to GitHub: $e') to include the exception
details/stack or relevant response info where available. Target symbols:
_client.functions.invoke, functionsResponse, createdTicket, ticketId.
- Around line 1210-1216: The relational join in the Supabase query (the select
using creator:users!tasks_created_by_fkey(...) and
assignee:users!tasks_assigned_to_fkey(...), stored in the local variable query
in supabase_service.dart) fails because the current FK constraints point to
auth.users rather than public.users; update your database schema migrations to
drop and recreate the tasks_created_by_fkey and tasks_assigned_to_fkey
constraints so they reference the public.users(id) table (and apply the same
change for tickets_created_by_fkey, tickets_assigned_to_fkey, and
meetings_created_by_fkey), run the migration, and then re-run the code that
builds query so the PostgREST relational joins resolve and the N+1 optimization
works.

In `@supabase/functions/create-github-issue/index.ts`:
- Line 15: Validate the incoming payload after destructuring const { title,
description, category, priority, ticketId } = await req.json() by checking
required fields (at minimum title and description) and reject requests with a
400 response and a clear error message if they are missing or empty; update the
handler that uses these variables to perform this validation early, trimming
strings and ensuring types (e.g., title is a non-empty string) before proceeding
to create the GitHub issue so you never call the issue-creation logic with
undefined values.

In `@supabase/migrations/20260309152700_add_github_issue_to_tickets.sql`:
- Around line 1-4: Add a non-unique index on tickets.github_issue_number to
speed webhook lookups that update by that column: modify the migration to
include a CREATE INDEX (preferably CREATE INDEX CONCURRENTLY IF NOT EXISTS) on
github_issue_number for the tickets table and ensure the migration's rollback
drops that index; reference the tickets table and github_issue_number column
when adding the index so the webhook handler's lookup is accelerated without
locking the table during migration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb96ab44-8a52-4566-95bd-5309ad4c9154

📥 Commits

Reviewing files that changed from the base of the PR and between 5afe656 and fc0e836.

📒 Files selected for processing (3)
  • lib/services/supabase_service.dart
  • supabase/functions/create-github-issue/index.ts
  • supabase/migrations/20260309152700_add_github_issue_to_tickets.sql

Comment thread lib/services/supabase_service.dart
Comment thread lib/services/supabase_service.dart
Comment thread supabase/functions/create-github-issue/index.ts
Comment on lines +1 to +4
-- Migration to add GitHub issue tracking to tickets
ALTER TABLE tickets
ADD COLUMN github_issue_number TEXT,
ADD COLUMN github_issue_url TEXT;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding an index on github_issue_number for webhook lookups.

Based on learnings, the GitHub webhook handler updates tickets by github_issue_number. If this lookup pattern is frequent, adding an index would improve query performance.

📈 Optional: Add index for webhook lookups
 -- Migration to add GitHub issue tracking to tickets
 ALTER TABLE tickets
 ADD COLUMN github_issue_number TEXT,
 ADD COLUMN github_issue_url TEXT;
+
+-- Index for efficient lookups by github_issue_number (used by webhook handler)
+CREATE INDEX idx_tickets_github_issue_number ON tickets(github_issue_number) WHERE github_issue_number IS NOT NULL;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Migration to add GitHub issue tracking to tickets
ALTER TABLE tickets
ADD COLUMN github_issue_number TEXT,
ADD COLUMN github_issue_url TEXT;
-- Migration to add GitHub issue tracking to tickets
ALTER TABLE tickets
ADD COLUMN github_issue_number TEXT,
ADD COLUMN github_issue_url TEXT;
-- Index for efficient lookups by github_issue_number (used by webhook handler)
CREATE INDEX idx_tickets_github_issue_number ON tickets(github_issue_number) WHERE github_issue_number IS NOT NULL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260309152700_add_github_issue_to_tickets.sql` around
lines 1 - 4, Add a non-unique index on tickets.github_issue_number to speed
webhook lookups that update by that column: modify the migration to include a
CREATE INDEX (preferably CREATE INDEX CONCURRENTLY IF NOT EXISTS) on
github_issue_number for the tickets table and ensure the migration's rollback
drops that index; reference the tickets table and github_issue_number column
when adding the index so the webhook handler's lookup is accelerated without
locking the table during migration.

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/create-github-issue/index.ts`:
- Around line 64-68: The code logs the raw GitHub API errorText when response.ok
is false (variables: response, errorText) which may expose sensitive repo
metadata; replace direct logging with a sanitized summary: read response.text()
into errorText, attempt JSON.parse(errorText) and extract known safe fields like
message or errors, fallback to stripping newlines and URLs and truncating to a
short length (e.g., 200 chars), then call console.error with the status and this
sanitized summary only and throw a new Error containing just the status (or the
sanitized short message) instead of the full raw response.
- Around line 17-37: Add validation for the variable category (used when
building the issue title like `"[${category}] ${title}"`) similar to
title/description: ensure category is a string and not just whitespace; if
invalid, either return a 400 response with a clear error (matching the other
validations) or coerce it to a safe default (e.g., "General") before
constructing the issue title to prevent "[undefined]" from appearing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7833fdd-92b0-47d9-a8ab-9a0f3540b19a

📥 Commits

Reviewing files that changed from the base of the PR and between fc0e836 and bb00b58.

📒 Files selected for processing (3)
  • lib/services/supabase_service.dart
  • supabase/functions/create-github-issue/index.ts
  • supabase/migrations/20260309152700_add_github_issue_to_tickets.sql

Comment on lines +17 to +37
// Strict Input Validation
if (!title || typeof title !== 'string' || title.trim() === '') {
return new Response(
JSON.stringify({ success: false, error: "Validation failed: 'title' must be a non-empty string." }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400 }
)
}

if (!description || typeof description !== 'string' || description.trim() === '') {
return new Response(
JSON.stringify({ success: false, error: "Validation failed: 'description' must be a non-empty string." }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400 }
)
}

if (!ticketId) {
return new Response(
JSON.stringify({ success: false, error: "Validation failed: 'ticketId' is required." }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400 }
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Good input validation, but category is not validated and could produce "[undefined]" in issue titles.

The validation for title, description, and ticketId is comprehensive. However, category is used directly in the issue title at line 58 without validation. If category is undefined or not a string, the GitHub issue title will be formatted as "[undefined] title".

🛡️ Proposed fix to add category validation
     if (!ticketId) {
       return new Response(
         JSON.stringify({ success: false, error: "Validation failed: 'ticketId' is required." }),
         { headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400 }
       )
     }
+
+    if (!category || typeof category !== 'string' || category.trim() === '') {
+      return new Response(
+        JSON.stringify({ success: false, error: "Validation failed: 'category' must be a non-empty string." }),
+        { headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 400 }
+      )
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/create-github-issue/index.ts` around lines 17 - 37, Add
validation for the variable category (used when building the issue title like
`"[${category}] ${title}"`) similar to title/description: ensure category is a
string and not just whitespace; if invalid, either return a 400 response with a
clear error (matching the other validations) or coerce it to a safe default
(e.g., "General") before constructing the issue title to prevent "[undefined]"
from appearing.

Comment on lines +64 to +68
if (!response.ok) {
const errorText = await response.text()
console.error(`GitHub API Error: ${response.status} ${errorText}`)
throw new Error(`GitHub API failed with status ${response.status}`)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider sanitizing logged error responses.

The GitHub API error text is logged directly at line 66. While this aids debugging, be aware that GitHub error responses could potentially include repository metadata. For production, consider logging only the status code and a sanitized summary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/create-github-issue/index.ts` around lines 64 - 68, The
code logs the raw GitHub API errorText when response.ok is false (variables:
response, errorText) which may expose sensitive repo metadata; replace direct
logging with a sanitized summary: read response.text() into errorText, attempt
JSON.parse(errorText) and extract known safe fields like message or errors,
fallback to stripping newlines and URLs and truncating to a short length (e.g.,
200 chars), then call console.error with the status and this sanitized summary
only and throw a new Error containing just the status (or the sanitized short
message) instead of the full raw response.

@VanshKaushal
Copy link
Copy Markdown
Author

All CodeRabbit review suggestions have been applied:

  1. Improved Edge Function logging
  2. Added strict payload validation
  3. Verified relational join FK safety
  4. Added webhook index for faster GitHub lookups

This branch is ready for re-review.

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