diff --git a/backlog/tasks/ml-171 - Fix-Oban-Web-crons-page-crash-MatchError-in-sparkline-from-SQLite-datetime-string.md b/backlog/tasks/ml-171 - Fix-Oban-Web-crons-page-crash-MatchError-in-sparkline-from-SQLite-datetime-string.md new file mode 100644 index 00000000..54843847 --- /dev/null +++ b/backlog/tasks/ml-171 - Fix-Oban-Web-crons-page-crash-MatchError-in-sparkline-from-SQLite-datetime-string.md @@ -0,0 +1,72 @@ +--- +id: ML-171 +title: >- + Fix Oban Web crons page crash: MatchError in sparkline from SQLite datetime + string +status: To Do +assignee: [] +created_date: "2026-05-09 05:28" +labels: + - bug + - oban-web + - sqlite +dependencies: [] +references: + - >- + https://github.com/oban-bg/oban_web/blob/main/lib/oban/web/live/crons/table_component.ex#L78-L85 + - >- + https://github.com/oban-bg/oban_web/blob/main/lib/oban/web/queries/cron_query.ex#L164-L177 +modified_files: + - lib/music_library_web/live/oban/crons/table_component.ex +priority: medium +--- + +## Description + + + +## Summary + +The `/dev/oban/crons` dashboard page crashes with `Elixir.MatchError` when rendering cron job history sparklines. This happens because Oban Web's `TableComponent.sparkline/1` calls `DateTime.from_naive!/2` which expects a `NaiveDateTime` struct, but receives a raw ISO 8601 string from SQLite. + +## Root Cause + +In `Oban.Web.CronQuery.cron_history/2` (deps/oban_web/lib/oban/web/queries/cron_query.ex:164-177), the query selects: + +```elixir +finished_at: fragment("COALESCE(?, ?, ?)", j.completed_at, j.cancelled_at, j.discarded_at) +``` + +SQLite stores `utc_datetime_usec` fields as ISO 8601 text. While regular field references like `j.scheduled_at` are properly decoded to `DateTime` structs by Ecto, the `fragment()` call bypasses type decoding and returns a raw string like `"2026-05-08T11:00:00.708342Z"`. + +The sparkline function at `Oban.Web.Crons.TableComponent` line 81 then fails: + +```elixir +(job.finished_at || job.attempted_at || job.scheduled_at) +|> DateTime.from_naive!("Etc/UTC") # 💥 expects NaiveDateTime, gets string +``` + +## Error Details + +- **ErrorTracker ID**: 4039 +- **Occurrences**: 2 (both on 2026-05-08) +- **View**: Oban.Web.DashboardLive (crons page) +- **Fingerprint**: `2C5253F1AB9B5D61231B7579E912FD2F7C155738DBB4325275F14E844C5D7B0C` + +## Approach + +The `sparkline/1` function is marked as `@doc overridable: 1` in Oban Web, so we can override it in the app. The fix should handle both `DateTime` structs (PostgreSQL) and ISO 8601 strings (SQLite) by parsing strings through `DateTime.from_iso8601!/1` first. + +Alternatively, the fix could be applied upstream in Oban Web's `CronQuery` to use a typed fragment or cast the `COALESCE` result. + + + +## Acceptance Criteria + + + +- [ ] #1 Visiting /dev/oban/crons no longer crashes with MatchError +- [ ] #2 Cron history sparklines render correctly showing job state colors +- [ ] #3 The fix handles both DateTime structs (PostgreSQL) and ISO 8601 strings (SQLite) +- [ ] #4 The overridden sparkline/1 function preserves the same visual output as upstream +