Skip to content

Commit 637b3b8

Browse files
committed
Nudgarr v5.0.1 — Patch Release
Patch: Library search & imports filtering (v5.0.1 follow-up) Imports Title search now works: GET /api/stats accepts a search query param; get_confirmed_entries() filters with LOWER(title) LIKE (substring, case-insensitive). The UI sends importsSearch on refresh. Imports · CF Score · Exclusions Search box + clear: Replaced x-model.debounce + immediate @input refresh (stale value) with x-model + @input.debounce on refresh, matching Library History. Typing and clearing the field now match what the server returns. Docs CHANGELOG (v5.0.1), CONTRIBUTING (/api/stats params), and entries.py module notes updated. No DB migrations; restart / redeploy to pick up changes.
1 parent d2d3967 commit 637b3b8

5 files changed

Lines changed: 22 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ All notable changes to Nudgarr are documented here.
1111
- **Library History:** `GET /api/state/items` now applies `type` (Cutoff Unmet, Backlog, CF Score) and `search` (case-insensitive title substring). `get_search_history()` in `db/history.py` implements filters; Cutoff Unmet matches DB values `Cutoff`, `Cutoff Unmet`, and empty legacy `sweep_type`. History title search uses immediate `x-model` with debounced refresh so the request uses the typed text.
1212
- **CF Score table:** Column sort (`sort` / `dir`) and title `search` are applied in SQL; `total` for pagination is a filtered count via `count_cf_score_entries()`. **Instance filter** dropdown uses `app|normalised_url` (`cfKey` on each `allInstances` row) to match `arr_instance_id` in `cf_score_entries` (same format as `cf_score_syncer._make_instance_id`), fixing per-instance filter when not on “All Instances”.
1313
- **Library Exclusions:** Sortable columns (title, search count, excluded date); **Unexclude** uses the green `.btn.ok` style; `unexcludeItem` refreshes the filtered paginated list.
14-
- **Library Imports:** Turnaround column is sortable (uses numeric `turnaround_days`).
14+
- **Library Imports:** Turnaround column is sortable (uses numeric `turnaround_days`). Title **`search`** query param wired through `GET /api/stats` to `get_confirmed_entries()`. Imports / CF Score / Exclusions title fields use immediate `x-model` with **`@input.debounce`** on refresh (not debounced `x-model` with immediate `@input`) so typing and clearing the search box match the data Nudgarr fetches.
1515
- **Default panel:** `VALID_TABS` in `constants.py` includes v5 panels `library` and `pipelines` (and keeps legacy `history`, `imports`, `cf-scores`) so saving Advanced “Default Panel” validates correctly.
1616
- **Docs:** `CONTRIBUTING.md` documents History/CF query params and `allInstances` `key` vs `cfKey`.
1717
- **Tests:** `tests/test_config_validation.py` asserts `library` and `pipelines` as valid `default_tab`; `tests/test_frontend_structure.py` raises the `app.js` line ceiling to match current file size.

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ nudgarr/ <- Python package
8282
state.py <- /api/state/*, /api/state/clear, /api/file/*, /api/exclusions*,
8383
/api/arr-link; GET /api/state/items passes type & search query
8484
params through to get_search_history()
85-
stats.py <- /api/stats, /api/stats/clear, check-imports
85+
stats.py <- /api/stats (instance, type, search, period, pagination),
86+
/api/stats/clear, check-imports
8687
intel.py <- /api/intel, /api/intel/reset
8788
cf_scores.py <- /api/cf-scores/status, /api/cf-scores/entries (instance_id,
8889
search, sort, dir), /api/cf-scores/scan, /api/cf-scores/reset

nudgarr/db/entries.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
upsert_stat_entry() -- insert or update the active (unimported) row
77
confirm_stat_entry() -- mark a row as imported; insert quality_history row
88
get_unconfirmed_entries() -- entries eligible for import checking
9-
get_confirmed_entries() -- paginated confirmed imports with quality history
9+
get_confirmed_entries() -- paginated confirmed imports with quality history;
10+
optional title_search (substring)
1011
get_period_totals() -- confirmed import counts for a rolling day window
1112
rename_instance_in_history() -- update instance name after a rename
1213
clear_stat_entries() -- delete all rows
@@ -332,13 +333,14 @@ def get_confirmed_entries(
332333
offset: int = 0,
333334
limit: int = 25,
334335
period_days: Optional[int] = None,
336+
title_search: str = "",
335337
) -> Tuple[int, List[Dict], List[str]]:
336338
"""Return paginated confirmed (imported) stat entries with optional filters.
337339
Returns a three-tuple: (total, entries, available_types) where total is the
338-
count matching instance / type / period filters, entries is the current page
340+
count matching instance / type / period / title_search filters, entries is the current page
339341
of dicts with a computed turnaround field and quality_history list, and
340342
available_types is the distinct list of entry types present for the current
341-
instance and period filters (type filter is not applied to that list).
343+
instance and period filters (type filter and title_search are not applied to that list).
342344
343345
When ``period_days`` is 7 or 30, only rows with ``imported_ts`` within the
344346
last N days are included (same cutoff as ``get_period_totals``). When
@@ -363,7 +365,11 @@ def get_confirmed_entries(
363365
if type_filter:
364366
where.append("se.type = ?")
365367
params.append(type_filter)
366-
where_sql = "WHERE " + " AND ".join(where)
368+
q = (title_search or "").strip()
369+
if q:
370+
where.append("LOWER(se.title) LIKE ?")
371+
params.append("%" + q.lower() + "%")
372+
where_sql = "WHERE " + " AND ".join(where)
367373

368374
total = conn.execute(
369375
f"SELECT COUNT(*) FROM stat_entries se {where_sql}", params

nudgarr/routes/stats.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
POST /api/stats/clear -- clear all stat entries (preserves lifetime totals)
88
POST /api/stats/check-imports -- manually trigger import check now
99
10-
The GET endpoint accepts an optional `period` query param (lifetime, 30, 7).
10+
The GET endpoint accepts optional query params: `period` (lifetime, 30, 7),
11+
`instance`, `type`, `search` (case-insensitive title substring on confirmed imports),
12+
plus `offset` and `limit` for pagination.
1113
Lifetime returns the persistent lifetime totals; 30 and 7 return rolling window
1214
counts calculated from imported_ts in stat_entries.
1315
"""
@@ -51,12 +53,15 @@ def api_get_stats():
5153
elif period == "30":
5254
period_days = 30
5355

56+
title_search = request.args.get("search", "").strip()
57+
5458
total, entries, available_types = db.get_confirmed_entries(
5559
instance_url_filter=instance_filter,
5660
type_filter=type_filter,
5761
offset=offset,
5862
limit=limit,
5963
period_days=period_days,
64+
title_search=title_search,
6065
)
6166

6267
# Resolve totals for the requested period.

nudgarr/templates/ui.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@
358358
<div class="field"><select x-model.number="importsPageSize" @change="importsPage=0;refreshImports()" style="width:auto">
359359
<option value="10">10</option><option value="25">25</option><option value="50">50</option>
360360
</select></div>
361-
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model.debounce.400ms="importsSearch" @input="importsPage=0;refreshImports()"></div>
361+
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model="importsSearch" @input.debounce.400ms="importsPage=0;refreshImports()"></div>
362362
<button class="btn sm ac" @click="checkImportsNow()">Refresh</button>
363363
</div>
364364
<div class="table-wrap">
@@ -427,7 +427,7 @@
427427
<template x-for="inst in allInstances" :key="inst.cfKey"><option :value="inst.cfKey" x-text="inst.name"></option></template>
428428
</select></div>
429429
<div class="field"><select x-model.number="cfPageSize" @change="cfPage=0;refreshCfScores()" style="width:auto"><option value="25">25</option><option value="50">50</option></select></div>
430-
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model.debounce.400ms="cfSearch" @input="cfPage=0;refreshCfScores()"></div>
430+
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model="cfSearch" @input.debounce.400ms="cfPage=0;refreshCfScores()"></div>
431431
</div>
432432
<div class="table-wrap">
433433
<table>
@@ -477,7 +477,7 @@
477477
<div class="field"><select x-model.number="exclPageSize" @change="exclPage=0;refreshExclusions()" style="width:auto">
478478
<option value="10">10</option><option value="25">25</option><option value="50">50</option>
479479
</select></div>
480-
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model.debounce.400ms="exclSearch" @input="exclPage=0;refreshExclusions()"></div>
480+
<div class="field" style="flex:1;min-width:180px"><input type="text" placeholder="Search title&hellip;" x-model="exclSearch" @input.debounce.400ms="exclPage=0;refreshExclusions()"></div>
481481
<button class="btn sm dng" @click="openClearExclModal()">Clear Exclusions</button>
482482
</div>
483483
<div class="table-wrap">

0 commit comments

Comments
 (0)