From a4b07b9600de59525ae613781c916eaecbaf4fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Mon, 15 Jun 2026 00:04:35 +0200 Subject: [PATCH] Fix job filtering crash on tuple module keys A module key can be a `{module, key}` tuple (e.g. `{Bob.Job.BuildDockerErlang, "amd64"}`). Filtering the queue by such a key built `where: j.module_key in ^[{module, key}]`, and Ecto rejects a single-tuple list because it reads as a keyword list, raising Ecto.QueryError. In the jobs UI this crashed the LiveView, which silently re-mounted and dropped the selection, so the filter appeared to do nothing. Match each key with `==` so the Term type dumps it as a single value instead of using `in` over a list. --- lib/bob/queue.ex | 13 ++++++++++++- test/bob/queue_test.exs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/bob/queue.ex b/lib/bob/queue.ex index 77e8f594..64c7dcab 100644 --- a/lib/bob/queue.ex +++ b/lib/bob/queue.ex @@ -232,7 +232,18 @@ defmodule Bob.Queue do end defp filter_modules(query, []), do: query - defp filter_modules(query, modules), do: where(query, [j], j.module_key in ^modules) + + defp filter_modules(query, modules) do + # A module key can be a `{module, key}` tuple, and a list of those reads as a + # keyword list, which Ecto's `in` rejects. Match each key with `==` so the + # Term type dumps it as a single value instead. + conditions = + Enum.reduce(modules, dynamic(false), fn module, acc -> + dynamic([j], ^acc or j.module_key == ^module) + end) + + where(query, ^conditions) + end defp finish(id, state) do now = DateTime.utc_now() diff --git a/test/bob/queue_test.exs b/test/bob/queue_test.exs index 9336db90..389a56c7 100644 --- a/test/bob/queue_test.exs +++ b/test/bob/queue_test.exs @@ -364,6 +364,24 @@ defmodule Bob.QueueTest do assert length(Queue.running([])) == 2 end + test "filters by a list of only tuple module keys" do + key = {TestJob, :variant} + Queue.add(key, [:a]) + Queue.add(Bob.Job.OTPChecker, [:b]) + + assert [j] = Queue.queued_listing(100, 0, [key]) + assert j.module_key == key + + {:ok, {id, _}} = Queue.start(key) + assert [r] = Queue.running([key]) + assert r.module_key == key + + Queue.success(id) + assert [p] = Queue.recent(50, 0, [key]) + assert p.module_key == key + assert Queue.finished_count([key]) == 1 + end + test "recent/3 and finished_count/1 filter by module" do Queue.add(TestJob, [:a]) {:ok, {id, _}} = Queue.start(TestJob)