Skip to content

feat(monitor): add a filtered job count - #9

Open
pdevito3 wants to merge 1 commit into
mainfrom
feat/job-count
Open

pdevito3 wants to merge 1 commit into
mainfrom
feat/job-count

Conversation

@pdevito3

@pdevito3 pdevito3 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The Monitor API can now count the jobs that match a filter. Before this change, a caller had to page through ListJobsAsync to get a total, and the page size capped each page.

 BackWaveMonitor
   ListJobsAsync(JobQuery?)              -> page of JobSnapshot
+  GetJobCountAsync(JobQuery?)           -> long
 IJobStore
   ListJobsAsync(JobQuery)
+  CountMatchingJobsAsync(JobQuery)      -> default: pages through ListJobsAsync
 InMemory / SQLite / Postgres / SQL Server / Oracle
+  CountMatchingJobsAsync                -> one SELECT COUNT(*) over AppendScopeConditions
 Pro MCP
+  count_jobs(state, queue, wire_name, schedule_id, tags)
 Sample.Api
+  GET /monitor/jobs/count?state=&queue=&tenant=

Rules of the count:

  • It uses the scope filters of the query: State, Queue, WireName, ScheduleId, and TagPredicates.
  • It ignores the paging fields: AfterSequence, SortDirection, and MaxResults.
  • MaxMonitorPageSize does not cap it.
  • A null query counts all jobs.

The store method has its own name, CountMatchingJobsAsync. The name CountJobsAsync already returns the queue depths, and two return shapes under one name would confuse callers. The dashboard does not change: a count on each Jobs page view would scan the table on a large store.

Evidence

Before: no count API. GET /monitor/jobs?state=Succeeded returns 200 rows (the page cap), and there is no total.

After: a real Sample.Api on SQLite (verify-backwave run). The store has 1,519 jobs from /demo/seed and /jobs/tagged-report:

Filter GET /monitor/jobs/count MCP count_jobs SQL on the run's database
none 1519 1519 count(*) = 1519
state=Succeeded 356 356 state = 3 = 356
queue=critical 349 349 queue = 'critical' = 349
tenant=acme 29 29 distinct tagged jobs = 29
queue=nowhere 0 - -

The listing with state=Succeeded returns 200 rows. The count returns 356.

New tests:

Conformance (all adapters)
  CountMatchingJobs_EqualsTheListing_ForEveryFilterKind     count == ListJobsAsync(q).Count for 11 filters
  CountMatchingJobs_IgnoresPaging_AndIsNotCappedByThePageBound
  CountMatchingJobs_EmptyStore_IsZero
MonitorApiTests
  GetJobCount_CountsTheFilteredPopulation_PastThePageBound
  GetJobCount_OnAStoreWithoutACountOverride_PagesThroughTheListing   (the default method)
Pro.Mcp
  CountJobs_CountsEveryMatch_PastTheStoreCap_WithTheSearchFilters
  CountJobs_InvalidStateAndTag_AreInvalidInputErrors
  count_jobs is hidden when the view gate denies

Local runs (dotnet build gives 0 warnings and 0 errors):

Project Result
BackWave.Tests (InMemory) 886 passed
BackWave.Sqlite.Tests 297 passed
BackWave.Postgres.Tests (live) 257 passed
BackWave.SqlServer.Tests (live) 264 passed
BackWave.Oracle.Tests (live) 290 passed
BackWave.Pro.Mcp.Tests 141 passed
BackWave.Hosting.Tests 86 passed
BackWave.Dashboard.Tests / Pro.Dashboard.Tests 108 / 29 passed
EF Core / SchemaGate / Upgrade / SourceGenerators / Pro / Testing 16 / 10 / 3 / 43 / 12 / 29 passed

Merge Danger

Door: two-way

The change adds members and changes no schema. A revert removes the members.

Blast Radius: small

  • IJobStore gets a new member with a default body, so a custom store still compiles. It gets the paging default until it overrides the method.
  • The MCP read surface grows from 14 to 15 tools for an unconfigured host. An MCP client sees one more tool.

Add BackWaveMonitor.GetJobCountAsync. It returns the number of jobs that
match the scope filters of a JobQuery: state, queue, wire name, schedule
id, and tag predicates. It ignores the paging fields (AfterSequence,
SortDirection, MaxResults), and the monitor page size does not cap it.

Add IJobStore.CountMatchingJobsAsync as a default interface method. The
default pages through ListJobsAsync, so a custom store keeps compiling
and gives the correct count. InMemory, SQLite, Postgres, SQL Server, and
Oracle override it with one COUNT(*) that uses the same scope conditions
as the listing. Every forwarding store wrapper in the tests forwards the
new member.

Add three conformance facts: the count equals the listing for each
filter kind, the count ignores the paging fields, and an empty store
gives 0.

Add the read-only MCP tool count_jobs. It takes the same filters as
search_jobs and uses the view gate. Add GET /monitor/jobs/count to the
sample.
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