feat: optional GitHub issue creation when creating tickets#215
feat: optional GitHub issue creation when creating tickets#215dolliecoder wants to merge 6 commits intoAOSSIE-Org:mainfrom
Conversation
📝 WalkthroughWalkthroughAdds optional GitHub issue creation when creating tickets: UI toggle, Supabase service updates to call a new Edge Function, a Deno Edge Function that creates GitHub issues, and a DB migration to store issue number and URL. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as Create Ticket Screen
participant Backend as Supabase Service
participant EdgeFunc as GitHub Edge Function
participant GitHubAPI as GitHub API
participant DB as Database
User->>UI: fill form + toggle GitHub option
UI->>Backend: createTicket(..., createGithubIssue=true)
Backend->>DB: insert ticket
DB-->>Backend: created ticket
alt createGithubIssue enabled
Backend->>EdgeFunc: POST (title, description, ticketNumber)
EdgeFunc->>GitHubAPI: POST /repos/{owner}/{repo}/issues
GitHubAPI-->>EdgeFunc: issue_number, issue_url
EdgeFunc-->>Backend: issue details
Backend->>DB: update ticket with github_issue_number & github_issue_url
DB-->>Backend: updated
else GitHub creation fails
EdgeFunc-->>Backend: error
Backend->>Backend: log error (non-blocking)
end
Backend-->>UI: ticket (with GitHub fields if available)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/services/supabase_service.dart (1)
1740-1787:⚠️ Potential issue | 🟠 MajorKeep the GitHub step off the ticket-creation critical path.
Line 1741 has already inserted the ticket, but Lines 1753-1787 still await the optional Edge Function before returning success. If that call is slow or hangs, the UI keeps spinning and users can retry even though the ticket already exists. Add a tight timeout here or move the GitHub sync to a background/server-side job so the main ticket flow stays resilient.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/supabase_service.dart` around lines 1740 - 1787, The GitHub Edge Function call (via _client.functions.invoke('create-github-issue') inside the create ticket flow) is blocking the main ticket creation path—move it off the critical path by executing the GitHub sync asynchronously with a short timeout or background retry: wrap the invoke call in a cancellable async task with a tight timeout (e.g., using a Future.timeout) so the function errors out quickly and does not delay returning the created ticket, or better, enqueue a background job (e.g., push a message to a server-side job/queue) that will call create-github-issue and update the tickets table (the .update on 'tickets' that sets github_issue_number/github_issue_url) separately; ensure createGithubIssue boolean still gates the background action but that the method returns success immediately after inserting the ticket and obtaining ticketId.
🧹 Nitpick comments (1)
lib/services/supabase_service.dart (1)
2330-2355: Choose one not-found contract forgetMeetingDetails.Line 2335 throws
Exception('Meeting not found'), but Lines 2353-2355 immediately catch it and returnnull, so callers still cannot distinguish not-found from other failures.lib/screens/meetings/meeting_detail_screen.dart:54-121already branches onnull, whilelib/screens/meetings/meeting_insights_screen.dart:44-62relies on exceptions; the service should expose one consistent contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/supabase_service.dart` around lines 2330 - 2355, getMeetingDetails currently throws Exception('Meeting not found') then catches everything and returns null, creating an inconsistent contract; change it to return null when the meeting is not found and rethrow other unexpected errors so callers can distinguish "not found" from real failures. Specifically, in getMeetingDetails replace the throw Exception('Meeting not found') with an immediate return null for the not-found path, and update the catch block (around the _client call and creator lookup) to rethrow the caught exception instead of returning null for non-not-found errors; keep the 'creator' resolution logic intact so the returned Map<String, dynamic> still includes 'creator' when present.
🤖 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`:
- Line 1239: The task payload includes an unsupported "priority" field and sets
status to 'open', which doesn't match the tasks schema (allowed statuses:
'todo', 'in_progress', 'completed'); remove the "priority" parameter (the String
priority = 'medium' argument) from the task creation call/handler and any
insertion of a priority column, and change the inserted status value from 'open'
to one of the allowed enums (e.g., 'todo') so the payload matches
sqls/03_task_schema.sql; update all occurrences (the parameter named priority
and the literal 'open' used for status) accordingly.
In `@supabase/functions/create-github-issue/index.ts`:
- Around line 5-8: The handler currently trusts client-provided
title/description/ticketNumber (body.title, body.description, body.ticketNumber)
and posts them directly to GitHub; change it to accept only a ticket ID (e.g.,
body.ticketId), load the ticket server-side (from your DB/Supabase) and derive
title/description/ticketNumber from that ticket record, validate the ticket
exists and caller has permission, and then use those server-loaded fields in the
GitHub POST block instead of the original body values; also return a clear error
if the ticket is missing/unauthorized before attempting the GitHub request.
- Around line 37-45: The issue is that the GitHub issue body template in the
JSON.stringify call for the request (using variables title, description,
ticketNumber) can interpolate the literal string "undefined" when description or
ticketNumber are missing; update the code that builds the body (where body:
JSON.stringify({...}) is created in the create-github-issue handler) to only
include the Ticket and Description lines when ticketNumber/description are
present — e.g., build the body string dynamically (or use nullish
coalescing/defaults) so Ticket and Description sections are omitted or use empty
strings instead of interpolating undefined, ensuring the resulting issue text
never contains "undefined".
---
Outside diff comments:
In `@lib/services/supabase_service.dart`:
- Around line 1740-1787: The GitHub Edge Function call (via
_client.functions.invoke('create-github-issue') inside the create ticket flow)
is blocking the main ticket creation path—move it off the critical path by
executing the GitHub sync asynchronously with a short timeout or background
retry: wrap the invoke call in a cancellable async task with a tight timeout
(e.g., using a Future.timeout) so the function errors out quickly and does not
delay returning the created ticket, or better, enqueue a background job (e.g.,
push a message to a server-side job/queue) that will call create-github-issue
and update the tickets table (the .update on 'tickets' that sets
github_issue_number/github_issue_url) separately; ensure createGithubIssue
boolean still gates the background action but that the method returns success
immediately after inserting the ticket and obtaining ticketId.
---
Nitpick comments:
In `@lib/services/supabase_service.dart`:
- Around line 2330-2355: getMeetingDetails currently throws Exception('Meeting
not found') then catches everything and returns null, creating an inconsistent
contract; change it to return null when the meeting is not found and rethrow
other unexpected errors so callers can distinguish "not found" from real
failures. Specifically, in getMeetingDetails replace the throw
Exception('Meeting not found') with an immediate return null for the not-found
path, and update the catch block (around the _client call and creator lookup) to
rethrow the caught exception instead of returning null for non-not-found errors;
keep the 'creator' resolution logic intact so the returned Map<String, dynamic>
still includes 'creator' when present.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e3fb10f3-8d08-49c8-877b-24bf7fafed85
📒 Files selected for processing (5)
lib/screens/tickets/create_ticket_screen.dartlib/services/supabase_service.dartsupabase/.env.examplesupabase/functions/create-github-issue/index.tssupabase/migrations/add_github_fields_to_tickets.sql
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
supabase/functions/create-github-issue/index.ts (1)
5-8:⚠️ Potential issue | 🟠 MajorCreate issues from a server-loaded ticket, not raw request fields.
Any caller that can invoke this function can still open arbitrary GitHub issues with the repo token because the handler trusts client-supplied
title,description, andticketNumber. Accept aticketId, load the ticket server-side, and derive the GitHub payload from that record after checking the caller is allowed to use it.Also applies to: 35-52
🤖 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 5 - 8, Current handler reads client-supplied title/description/ticketNumber from req.json() (const title/description/ticketNumber) which allows callers to create arbitrary GitHub issues; instead accept a ticketId, fetch the ticket record server-side (e.g. via your Supabase client or getTicketById), verify the caller is authorized to act on that ticket, and build the GitHub payload (title, body, labels) from the loaded ticket fields before calling the GitHub API; remove trust of client-provided title/description/ticketNumber and replace their usage in the issue-creation flow with the server-loaded ticket data and authorization check.
🤖 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 1748-1792: The method currently returns createdTicket captured
before updating github_issue_number/github_issue_url, causing stale data to be
returned; after the _client.from('tickets').update(...).eq('id', ticketId) call
(inside the createGithubIssue block) either re-query the updated row (e.g.,
select from 'tickets' where id == ticketId) or merge the returned github fields
(data['issue_number'], data['issue_url']) into createdTicket (or replace
createdTicket with the fresh row) so the returned map includes the persisted
github_issue_number and github_issue_url; update references to
createdTicket/ticketId accordingly and ensure the final returned 'ticket'
contains the post-update values.
- Around line 1751-1785: The current flow invokes
_client.functions.invoke('create-github-issue', ...) which creates a GitHub
issue before updating the tickets row (tickets table) and silently swallows
failures, allowing orphaned issues; fix by making the operation atomic: either
(A) change the edge function 'create-github-issue' to accept ticketId and
perform the GitHub issue creation plus the tickets row update inside the edge
function (so the edge function does create+update and returns success/failure),
or (B) add an idempotency/existence check in the client before calling the
function (check tickets row for existing github_issue_number on
ticketId/createdTicket) and only call the edge function if no issue exists, and
handle update failures by retrying or surfacing the error instead of swallowing
it in the catch block; update references to createdTicket, ticketId,
createGithubIssue, and the invoke call accordingly.
In `@supabase/functions/create-github-issue/index.ts`:
- Around line 35-54: Wrap the GitHub POST fetch (the call that assigns
githubResponse) with an AbortController timeout: create an AbortController, pass
controller.signal into the fetch options, start a setTimeout that calls
controller.abort() after a sensible short timeout (e.g. 2–5s), and clear the
timeout on successful response; catch the abort/error from fetch and treat it as
"GitHub issue skipped" (log and continue) rather than failing the whole
ticket-creation flow so the earlier DB insert remains valid. Ensure the fetch
headers/body code remains unchanged and that you reference the same
githubResponse variable and request options when wiring up the signal and
timeout.
---
Duplicate comments:
In `@supabase/functions/create-github-issue/index.ts`:
- Around line 5-8: Current handler reads client-supplied
title/description/ticketNumber from req.json() (const
title/description/ticketNumber) which allows callers to create arbitrary GitHub
issues; instead accept a ticketId, fetch the ticket record server-side (e.g. via
your Supabase client or getTicketById), verify the caller is authorized to act
on that ticket, and build the GitHub payload (title, body, labels) from the
loaded ticket fields before calling the GitHub API; remove trust of
client-provided title/description/ticketNumber and replace their usage in the
issue-creation flow with the server-loaded ticket data and authorization check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 556000e1-e4d5-4842-b796-4fcd5494239d
📒 Files selected for processing (2)
lib/services/supabase_service.dartsupabase/functions/create-github-issue/index.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
lib/services/supabase_service.dart (1)
1748-1791:⚠️ Potential issue | 🟠 MajorThe new idempotency guard does not actually guard anything.
Line 1751 checks
createdTicket['github_issue_number']on the row that was just inserted, so it will still benullon the normal path and won't stop duplicate GitHub issue creation after partial failures or retries. If this needs to be idempotent, move the create+link step behindticketIdin the edge function, or re-read the persisted ticket byticketIdbefore invoking GitHub.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/supabase_service.dart` around lines 1748 - 1791, The current idempotency check inspects createdTicket (the just-inserted in-memory map) so it can’t detect prior partial completions; instead, after obtaining ticketId re-query the persisted row and check its 'github_issue_number' before calling _client.functions.invoke('create-github-issue'). If no github_issue_number exists, call the edge function and then persist the returned issue_number/issue_url using the existing .from('tickets').update(...).eq('id', ticketId) logic; alternatively move the create+link logic into the edge function so creation and linking happen atomically on the server side.
🧹 Nitpick comments (1)
lib/services/supabase_service.dart (1)
1294-1295: These “refresh” calls are currently just extra round-trips.
getTasks()andgetTickets()return lists, but their results are discarded here and those methods do not publish to_tasksStreamController/_ticketsStreamController. So Lines 1294-1295 and Line 1797 do not refresh any local state.♻️ Suggested cleanup
- // Refresh tasks - await getTasks(); + // Refresh tasks stream if callers depend on it + _tasksStreamController.add(await getTasks());- await getTickets(); + _ticketsStreamController.add(await getTickets());Also applies to: 1797-1797
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/supabase_service.dart` around lines 1294 - 1295, The calls to getTasks() and getTickets() are no-ops because their returned lists are discarded and those methods don't publish to _tasksStreamController/_ticketsStreamController; either remove the redundant await getTasks()/getTickets() calls or modify the call sites to publish the returned lists to the corresponding controllers (e.g., final tasks = await getTasks(); _tasksStreamController.add(tasks); and similarly for tickets with _ticketsStreamController.add(tickets)); update both places where these refresh calls appear so local state actually updates or the calls are eliminated if unused.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/services/supabase_service.dart`:
- Around line 1748-1791: The current idempotency check inspects createdTicket
(the just-inserted in-memory map) so it can’t detect prior partial completions;
instead, after obtaining ticketId re-query the persisted row and check its
'github_issue_number' before calling
_client.functions.invoke('create-github-issue'). If no github_issue_number
exists, call the edge function and then persist the returned
issue_number/issue_url using the existing .from('tickets').update(...).eq('id',
ticketId) logic; alternatively move the create+link logic into the edge function
so creation and linking happen atomically on the server side.
---
Nitpick comments:
In `@lib/services/supabase_service.dart`:
- Around line 1294-1295: The calls to getTasks() and getTickets() are no-ops
because their returned lists are discarded and those methods don't publish to
_tasksStreamController/_ticketsStreamController; either remove the redundant
await getTasks()/getTickets() calls or modify the call sites to publish the
returned lists to the corresponding controllers (e.g., final tasks = await
getTasks(); _tasksStreamController.add(tasks); and similarly for tickets with
_ticketsStreamController.add(tickets)); update both places where these refresh
calls appear so local state actually updates or the calls are eliminated if
unused.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ae91484d-8177-4ea4-ad8b-5716733d2766
📒 Files selected for processing (2)
lib/services/supabase_service.dartsupabase/functions/create-github-issue/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- supabase/functions/create-github-issue/index.ts
|
The ticket is freshly inserted so the field will be null on the normal path. A proper idempotent flow would require moving the logic into the edge function or re-fetching the ticket, which is outside the scope of this PR. |
Title: feat: optional GitHub issue creation when creating tickets
Closes #212
# 📝 Description
This PR introduces optional GitHub issue creation when creating tickets in Ell-ena.
The goal of this change is to improve traceability between Ell-ena's internal ticket system and GitHub development workflows. Users can now optionally create a GitHub issue at the same time they create a ticket, allowing teams to track tasks both inside Ell-ena and directly within their GitHub repository.
This PR builds on top of the GitHub integration infrastructure corrected in PR #213. The backend Edge Function handles secure communication with the GitHub API, while this PR integrates the Flutter UI and service layer to invoke that functionality during ticket creation.
The workflow is designed as follows:
ticketstable.The invocation from the Flutter service layer is handled through:
The ticket is then updated with the returned GitHub issue information:
This design ensures that:
GitHub tokens remain secure in Supabase environment variables
The frontend never directly communicates with GitHub APIs
Ticket creation still succeeds even if GitHub issue creation fails
The feature remains optional and does not affect existing workflows
🔧 Changes Made
Added a "Create GitHub Issue" checkbox to the ticket creation UI.
Integrated the ticket creation flow with the create-github-issue Supabase Edge Function.
Implemented conditional GitHub issue creation logic in SupabaseService.createTicket.
Stored GitHub issue metadata (github_issue_number, github_issue_url) in the tickets table.
Ensured ticket creation continues to work independently if GitHub issue creation fails.
Built on top of the backend GitHub integration corrected in PR #213.
📷 Screenshots or Visual Changes (if applicable)
The ticket creation screen now includes a "Create GitHub Issue" checkbox that allows users to optionally create a GitHub issue linked to the ticket.
🤝 Collaboration
Collaborated with: None (optional)
✅ Checklist
Summary by CodeRabbit
New Features
Bug Fixes
Chores