427905a00dc6799d3c8d750de5d9376bccc3c972 jnavarr5 Thu Apr 9 15:49:33 2026 -0700 Add tracker name resolution to redmineCli list command, refs #37339 Co-Authored-By: Claude Opus 4.6 (1M context) diff --git src/utils/redmineCli src/utils/redmineCli index a23e921b16e..909628e2c0b 100755 --- src/utils/redmineCli +++ src/utils/redmineCli @@ -26,30 +26,54 @@ import urllib.parse import urllib.request from datetime import datetime # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- DEFAULT_REDMINE = "https://redmine.gi.ucsc.edu" DEFAULT_PROJECT = "maillists" TRACKER_MLQ = 7 PRIORITY_UNPRIORITIZED = 12 STATUS_NEW = 1 +# Name -> Redmine tracker ID mapping (case-insensitive lookup in resolve_tracker) +TRACKER_IDS = { + "bug": 21, + "feature": 23, + "track": 11, + "hub": 45, + "data sets": 46, "datasets": 46, + "to do": 10, "todo": 10, + "docs": 25, + "assembly": 24, + "process": 12, + "meeting": 28, + "cr": 26, + "cgi build": 33, + "mlq": 7, + "mlq off list": 15, + "suggestion box": 44, + "github": 48, + "information": 35, "info": 35, + "build patch": 36, + "release": 47, + "housekeeping": 49, +} + # Name -> Redmine status ID mapping (case-insensitive lookup in resolve_status) STATUS_IDS = { "new": 1, "looking for dev": 37, "snoozed": 34, "limbo": 36, "researching/exploratory": 35, "researching": 35, "exploratory": 35, "masked 2bit file": 29, "initial sequence": 25, "minimal browser": 26, "docs in progress": 27, "on deck": 33, "in progress": 2, "stalled": 13, "qa ready": 10, "qa": 10, @@ -244,30 +268,41 @@ "Run 'redmineCli users' to see available names and IDs.") def resolve_status(name_or_id): """Resolve a status name to a Redmine status ID. Accepts name or numeric ID.""" if name_or_id.isdigit(): return int(name_or_id) key = name_or_id.lower().strip() if key in STATUS_IDS: return STATUS_IDS[key] sys.exit(f"Error: unknown status '{name_or_id}'. Known statuses: " + ", ".join(sorted(k for k in STATUS_IDS if " " in k or not any( k2 != k and STATUS_IDS[k2] == STATUS_IDS[k] for k2 in STATUS_IDS)))) +def resolve_tracker(name_or_id): + """Resolve a tracker name to a Redmine tracker ID. Accepts name or numeric ID.""" + if str(name_or_id).isdigit(): + return int(name_or_id) + key = str(name_or_id).lower().strip() + if key in TRACKER_IDS: + return TRACKER_IDS[key] + sys.exit(f"Error: unknown tracker '{name_or_id}'. Known trackers: " + + ", ".join(sorted(set(TRACKER_IDS.keys())))) + + def prepend_attribution(text): """Prepend 'From Claude:' attribution to text for write operations.""" return ATTRIBUTION + text def strip_emoji(text): """Strip 4-byte Unicode (emoji) that Redmine's MySQL may reject.""" if not text: return text return re.sub(r'[\U00010000-\U0010FFFF]', '', text) def read_text_input(direct, from_file): """Read text from --message/--description or --message-file/--description-file.""" if from_file: @@ -441,31 +476,31 @@ "limit": str(args.limit), "offset": str(args.offset), "sort": args.sort, } if args.status: params["status_id"] = args.status if args.assigned_to: if args.assigned_to.lower() == "me": params["assigned_to_id"] = "me" else: params["assigned_to_id"] = str(resolve_user(args.assigned_to)) if args.tracker: - params["tracker_id"] = args.tracker + params["tracker_id"] = resolve_tracker(args.tracker) if args.search: params["subject"] = f"~{args.search}" query = urllib.parse.urlencode(params) data = api_get(args.base_url, f"/issues.json?{query}", args.api_key) issues = data.get("issues", []) total = data.get("total_count", 0) if not issues: print("No issues found.") return out = []