219 lines
7.3 KiB
Markdown
219 lines
7.3 KiB
Markdown
# 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
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
<cache-dir>/<project>/<issue-id>.json
|
||
```
|
||
|
||
Example for the durable customer-service cache:
|
||
|
||
```text
|
||
/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:
|
||
|
||
```bash
|
||
./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)
|
||
|
||
1. Scope one project + one cache root.
|
||
2. Filter cached issues by `updated_on` window, status **name**, category **name**.
|
||
3. Sort and cap results.
|
||
4. Emit table, JSON, or JSONL.
|
||
5. 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` / `closed` shorthand** on search (only status **name**)
|
||
- **No remote/API filters** on `search`
|
||
(`--remote-updated-on`, `--remote-updated-since`, `--remote-status-id` are **sync-only**)
|
||
- **No relative time helpers** like `30d` / `2 days ago` on local search flags
|
||
(those helpers exist for **remote sync** convenience flags; local `--updated-since` wants 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:
|
||
|
||
```bash
|
||
./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
|
||
|
||
```bash
|
||
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.
|