7.3 KiB
Local search in redmine_tickets.py (pre-sanity snapshot)
Date: 2026-07-14
Status: documentation of current behavior before any search sanity/cleanup pass
Script: /home/jason/projects/redmine/redmine_tickets.py
Typical cache: /home/jason/projects/redmine/cache/redmine_tickets
This note captures what local search does and does not do as of 2026-07-14, after remote sync filters and run-log work landed. It is intentionally frank: docs had been thin; this is the baseline.
Mental model
sync = talk to Redmine → update local JSON cache
search = filter/list what is already cached
search is a cache browser / structured filterer, not:
- a live Redmine issue query tool
- full-text corpus search
- the semantic Redmine search path (that lives elsewhere)
If a ticket is not already under the cache directory for the selected project, search cannot see it.
Where data comes from
Cached issues live as one JSON file per ticket:
<cache-dir>/<project>/<issue-id>.json
Example for the durable customer-service cache:
/home/jason/projects/redmine/cache/redmine_tickets/customer-service/*.json
/home/jason/projects/redmine/cache/redmine_tickets/manifest.json
search loads via TicketCache.iter_cached_issues() (reads every *.json in the project subdir). It does not call Redmine and does not require REDMINE_API_KEY.
CLI surface (current)
Parent options still apply:
--base-url(unused by search, but accepted on the parent parser)--project--cache-dir
Subcommand:
./redmine_tickets.py \
--project customer-service \
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
search [filters...] [output...]
Filters (all local, AND’d)
| Flag | Matches on | Notes |
|---|---|---|
--updated-since |
issue updated_on ≥ value |
via parse_dt (absolute date/datetime) |
--updated-before |
issue updated_on ≤ value |
same |
--status |
status name | case-insensitive exact match (Closed, New, …) — not status_id |
--category |
category name | case-insensitive exact match |
Implementation: filter_issues() in redmine_tickets.py.
Order + limit
| Flag | Behavior |
|---|---|
--order newest-updated-first |
default |
--order oldest-updated-first |
|
--order id-asc / id-desc |
|
--order as-is |
filesystem glob order from iter_cached_issues |
--max-tickets N |
after filter + sort, keep first N |
Implementation: select_issues().
Output
| Mode | What you get |
|---|---|
| default table | #id, updated_on, status name, category name, subject (via print_issue_rows) |
--json |
full cached issue payloads as one JSON array |
--jsonl |
one full issue JSON object per line (good for piping) |
Human table is summary-only. Full subject/body/journals/helpdesk are only in --json / --jsonl.
What it allows (summary)
- Scope one project + one cache root.
- Filter cached issues by
updated_onwindow, status name, category name. - Sort and cap results.
- Emit table, JSON, or JSONL.
- Run offline against cache (no Redmine, no API key).
What it does not allow (current gaps)
This is the important half of the snapshot:
- No free-text search (subject / description / journals / from-address)
- No sender / email / contact filters
- No tracker, assignee, priority, tags
- No
status_id/open/closedshorthand on search (only status name) - No remote/API filters on
search
(--remote-updated-on,--remote-updated-since,--remote-status-idare sync-only) - No relative time helpers like
30d/2 days agoon local search flags
(those helpers exist for remote sync convenience flags; local--updated-sincewants a real parseable date/datetime) - No boolean logic (AND/OR/NOT nesting)
- No query language comparable to Redmine UI filters or
rg - No ranking / relevance
- No “search then sync missing ones”
- No pagination UI beyond
--max-tickets - No writes (search never mutates cache or Redmine)
Practical content search today
Pipe full payloads and use external tools:
./redmine_tickets.py \
--project customer-service \
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
search --jsonl | rg -i 'gaylord|rate sheet'
Or inspect per-ticket files under the project cache dir directly.
Examples that work
cd /home/jason/projects/redmine
# Everything currently cached for customer-service (table)
./redmine_tickets.py \
--project customer-service \
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
search
# Cached tickets updated since a date, newest first, cap 50
./redmine_tickets.py \
--project customer-service \
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
search \
--updated-since 2026-06-14 \
--max-tickets 50
# Only closed, as full JSONL for downstream tools
./redmine_tickets.py \
--project customer-service \
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
search \
--status Closed \
--jsonl
Related commands people confuse with “search”
| Command | Relation to local search |
|---|---|
sync |
fills the cache that search reads; supports server-side status_id + updated_on |
spam-check |
walks cache, scores spam; not general search |
known-spam-scan |
profile match over cache |
decompose |
one-ticket analysis, cache-first |
| semantic index / external search | separate stack; not this subcommand |
Remote vs local filter naming (context for sanity later)
As of the 2026-07-14 sync work:
| Side | Flags (examples) | Used by |
|---|---|---|
| Remote (API) | --status-id / --remote-status-id, --remote-updated-on, --remote-updated-since, --remote-updated-before |
sync list call only |
| Local (post) | --updated-since, --updated-before, --status, --category |
sync post-filter and search |
Help text for the shared local flags still says things like “applied after API list,” which is accurate for sync but slightly misleading when those same flags appear under search (where there is no API list). A future sanity pass may want search-specific wording.
Implementation pointers
| Piece | Location |
|---|---|
| Filter logic | filter_issues() |
| Sort / max | select_issues() |
| Cache read | TicketCache.iter_cached_issues() |
| Table output | print_issue_rows() |
| CLI wiring | build_parser() → search subparser; main() branch args.command == "search" |
| Shared filter args | add_common_filters() |
Bottom line (baseline for sanity)
Does: local structured filter/list over already-synced ticket JSON — date window, status name, category name, sort, limit, table/json/jsonl.
Doesn’t: live Redmine query, free-text, contact/sender, remote filters, relative-date helpers on local flags, or advanced query syntax.
Any “make search sane” work after this file should treat the above as the pre-change contract and call out intentional expansions (e.g. --from, --subject-contains, --text, relative dates on local filters) separately.