Skip to content

feat: optional GitHub issue creation when creating tickets#215

Open
dolliecoder wants to merge 6 commits intoAOSSIE-Org:mainfrom
dolliecoder:feat/optional-github-issue
Open

feat: optional GitHub issue creation when creating tickets#215
dolliecoder wants to merge 6 commits intoAOSSIE-Org:mainfrom
dolliecoder:feat/optional-github-issue

Conversation

@dolliecoder
Copy link
Copy Markdown
Contributor

@dolliecoder dolliecoder commented Mar 9, 2026

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:

  • A ticket is first created normally in the Supabase tickets table.
  • If the "Create GitHub Issue" checkbox is enabled, the client invokes the Supabase Edge Function.
  • The Edge Function creates a GitHub issue and returns metadata.
  • The returned issue details are stored back in the ticket record.

The invocation from the Flutter service layer is handled through:

await _client.functions.invoke(
  'create-github-issue',
  body: {
    'title': title,
    'description': safeDescription,
    'ticketNumber': ticketNumber,
  },
);

The ticket is then updated with the returned GitHub issue information:

await _client
  .from('tickets')
  .update({
    'github_issue_number': data['issue_number'],
    'github_issue_url': data['issue_url'],
  })
  .eq('id', ticketId);

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

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • GitHub issue integration — option to create a linked GitHub issue when creating tickets
    • Tasks can include an optional due date; task/ticket details show enriched creator and assignee info
  • Bug Fixes

    • Improved form validation null-safety and minor UI alignment/spacing adjustments
    • Lists refresh after creating tasks/tickets so new items appear immediately
  • Chores

    • Database schema and example environment added to support GitHub integration
    • New backend function added to create GitHub issues

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Ticket Creation UI
lib/screens/tickets/create_ticket_screen.dart
Added _createGithubIssue state and a "Create GitHub Issue" checkbox; adjusted form validation and spacing; pass createGithubIssue to backend create call.
Backend Service
lib/services/supabase_service.dart
Added createGithubIssue parameter to createTicket; calls Edge Function when enabled and updates ticket with github_issue_number/github_issue_url; added _getUserInfo helper; switched some retrievals from single()→list-based and added post-create refreshes.
Edge Function & Env
supabase/functions/create-github-issue/index.ts, supabase/.env.example
New Deno HTTP function that validates input, reads GITHUB_* env vars, calls GitHub Issues API and returns issue number/URL; added example env variables.
Database Migration
supabase/migrations/add_github_fields_to_tickets.sql
Adds github_issue_number (INTEGER) and github_issue_url (TEXT) columns to tickets table.

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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hop with keys and clever cheer,
I bind your ticket to GitHub near;
A tiny function sends the plea,
An issue blooms for all to see —
Hooray, the links appear!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main feature being added: optional GitHub issue creation when creating tickets.
Linked Issues check ✅ Passed All objectives from issue #212 are met: UI checkbox for GitHub issue creation, Supabase Edge Function implementation, database schema updates, and resilient ticket creation flow with GitHub metadata storage.
Out of Scope Changes check ✅ Passed All changes align with the scope of issue #212; no unrelated modifications detected beyond implementing GitHub issue creation for tickets.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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

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: 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 | 🟠 Major

Keep 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 for getMeetingDetails.

Line 2335 throws Exception('Meeting not found'), but Lines 2353-2355 immediately catch it and return null, so callers still cannot distinguish not-found from other failures. lib/screens/meetings/meeting_detail_screen.dart:54-121 already branches on null, while lib/screens/meetings/meeting_insights_screen.dart:44-62 relies 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

📥 Commits

Reviewing files that changed from the base of the PR and between 03efbbb and bc0b687.

📒 Files selected for processing (5)
  • lib/screens/tickets/create_ticket_screen.dart
  • lib/services/supabase_service.dart
  • supabase/.env.example
  • supabase/functions/create-github-issue/index.ts
  • supabase/migrations/add_github_fields_to_tickets.sql

Comment thread lib/services/supabase_service.dart Outdated
Comment thread supabase/functions/create-github-issue/index.ts Outdated
Comment thread supabase/functions/create-github-issue/index.ts Outdated
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: 3

♻️ Duplicate comments (1)
supabase/functions/create-github-issue/index.ts (1)

5-8: ⚠️ Potential issue | 🟠 Major

Create 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, and ticketNumber. Accept a ticketId, 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

📥 Commits

Reviewing files that changed from the base of the PR and between bc0b687 and c5a9ee8.

📒 Files selected for processing (2)
  • lib/services/supabase_service.dart
  • supabase/functions/create-github-issue/index.ts

Comment thread lib/services/supabase_service.dart
Comment thread lib/services/supabase_service.dart
Comment thread supabase/functions/create-github-issue/index.ts Outdated
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.

♻️ Duplicate comments (1)
lib/services/supabase_service.dart (1)

1748-1791: ⚠️ Potential issue | 🟠 Major

The 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 be null on 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 behind ticketId in the edge function, or re-read the persisted ticket by ticketId before 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() and getTickets() 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

📥 Commits

Reviewing files that changed from the base of the PR and between c5a9ee8 and 7e21b58.

📒 Files selected for processing (2)
  • lib/services/supabase_service.dart
  • supabase/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

@dolliecoder
Copy link
Copy Markdown
Contributor Author

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.

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.

FEATURE REQUEST: Add GitHub Issue Creation for Tickets using Supabase Edge Function

1 participant