gitlab-tracker-redmine
Optional Redmine integration plugin for gitlab-tracker.
Implements the TrackerProvider trait from gitlab-tracker-core to detect Redmine ticket references in MR titles and descriptions, and enrich the TUI with ticket details and time tracking.
Features
-
Linked ticket display in the Inspector's MR Info view — the following fields are shown when available:
Field Source ( GET /issues/{id}.json)Ticket ID + Subject id,subjectType badge tracker.name(e.g. "Bug", "Evolution")Priority badge priority.name(e.g. "Normal", "High")Status status.nameAuthor / Assignee author.name,assigned_to.nameTarget version fixed_version.nameStart date start_dateProgress bar done_ratio(0–100 %)Estimate / Spent / Remaining estimated_hours,spent_hours,remaining_hoursDirect URL built from url+ ticket IDType and Priority are rendered as coloured badges — colours are fully configurable in
projects.toml(see Configuration below). -
Multi-tenant: each GitLab project in
projects.tomlcan point to a different Redmine instance. The API token for each instance is stored separately in the OS keyring, keyed by the Redmine URL — no token collision between tenants. -
Change notifications: when a ticket field changes between two refresh cycles, a native desktop notification is raised with the before → after values. Tracked fields:
Field Notification icon Priority ⚠️ dialog-warningStatus ℹ️ dialog-informationAssignee ℹ️ dialog-informationTarget version ℹ️ dialog-informationProgress ( done_ratio)ℹ️ dialog-information— fires on both increase and decreaseNotifications are suppressed during the initial sync (see
gitlab-tracker-notify). -
Time Log view (
P× 2): pressPtwice (when the Tracker pane is focused) to reach the Time Log view:- A progress bar comparing time spent vs. the ticket's estimate.
- The full list of time entries (date, user, activity, duration, comment) fetched live from Redmine.
- Navigating between MRs with
↑/↓while on this view automatically refreshes the entries for the newly selected ticket.
-
Log time (
L): open a popup to submit a new time entry directly to Redmine — select the activity category, enter a duration (e.g.1h30,90m,1.5h), optionally add a comment, and confirm withEnter. -
Tracker column in the main table (toggleable via
C) — shows ticket ID, status, and spent/estimated time at a glance.
Enabling at Build Time
The redmine feature flag must be explicitly passed when building or installing:
# Build from source
# Install from source
# Install from crates.io
Without the flag, this crate is not compiled and there is zero runtime overhead.
Configuration
Configuration lives directly inside projects.toml under a [project.tracker] section — no separate file needed. Each [[project]] entry can point to a different Redmine instance.
On first launch with the feature enabled and no [project.tracker] section configured, the app interactively prompts for a Redmine URL:
🌐 No Redmine URL found for this project.
Leave empty to disable Redmine integration.
Redmine URL: https://redmine.my-company.com
Leaving the prompt empty silently disables the integration for that project — no error, no impact on the rest of the dashboard. You can also pre-configure via environment variable to skip the prompt entirely:
REDMINE_URL=https://redmine.my-company.com
Upgrading from a previous version? If you have a
redmine.yamlfile from an older release, the app performs a silent one-time migration on first startup: all settings are read fromredmine.yaml, written into the[project.tracker]section ofprojects.toml, and the old file is no longer used.
projects.toml — full Redmine reference
The Redmine section uses provider = "redmine" as its discriminant. All other fields are forwarded to the plugin.
[[]]
= "My Company — Backend"
= "https://gitlab.my-company.com"
= "12345678"
= true
[]
# Discriminant — selects the Redmine plugin.
= "redmine"
# Base URL of your Redmine instance. No trailing slash.
# Can also be set via the REDMINE_URL environment variable.
= "https://redmine.my-company.com"
# Regex patterns used to detect ticket IDs in MR titles and descriptions.
# Each pattern must expose the numeric ID in capture group 1.
# The first match wins; patterns are tried in order.
# Defaults shown below — omit the key to keep them.
= [
"#(\\d+)", # plain #1234
"(?i)(?:refs|fixes|closes|resolves)\\s+#(\\d+)", # refs #1234, fixes #1234, …
"/issues/(\\d+)", # full Redmine URL in description
]
# Badge colours for the "Type" field (tracker.name in the Redmine API).
# Keys are matched CASE-INSENSITIVELY. Use "*" as a catch-all fallback.
# Accepted colour values: named (red, cyan, dark_gray, …) or hex (#ff6600).
[]
= { = "red", = "white" }
= { = "cyan", = "black" }
= { = "yellow", = "black" }
= { = "dark_gray", = "white" }
# Badge colours for the "Priority" field (priority.name in the Redmine API).
[]
= { = "dark_gray", = "white" }
= { = "dark_gray", = "white" }
= { = "yellow", = "black" }
= { = "red", = "white" }
= { = "dark_gray", = "white" }
Multi-tenant example — two projects, two Redmine instances
[[]]
= "Client A — Backend"
= "https://gitlab.com"
= "12345678"
= true
[]
= "redmine"
= "https://redmine-a.example.com"
[[]]
= "Client B — Backend"
= "https://gitlab.my-company.com"
= "87654321"
[]
= "redmine"
= "https://redmine-b.example.com"
[]
= { = "red", = "white" }
Each instance has its own token stored independently in the OS keyring (keyed by URL). Switching the active project automatically uses the correct credentials.
Field reference
| Field | Required | Description |
|---|---|---|
provider |
✅ | Must be "redmine" to activate this plugin |
url |
✅ | Base URL of your Redmine instance (no trailing slash) |
ticket_patterns |
❌ | Regex list to detect ticket IDs — capture group 1 must match the numeric ID. Defaults to #1234, refs #1234, and full URL patterns |
tracker_type_colors |
❌ | Badge colour map for the tracker.name field. Keys are case-insensitive; "*" is a catch-all. Omit to use the default (dark_gray / white) |
priority_colors |
❌ | Badge colour map for the priority.name field. Same rules as above |
How to discover your Redmine's label values
Label names (tracker types, priorities) are instance-specific and may be in any language. Run these commands against any existing issue to see what your instance returns:
# Tracker type and priority of issue #1234 | # All priorities defined in your instance |
Estimate to Complete (ETC) — automatic update on time entry submission
When you log time via the L popup, the app automatically recomputes the Estimate to Complete (ETC) on the linked Redmine ticket and writes it back.
ETC (Estimate to Complete) is the standard project management term for the remaining effort needed to finish a task. It is sometimes labelled Remaining time or Reste à faire (RAF) in French Redmine instances.
How it works
The computation uses fields returned directly by the Redmine issue API (GET /issues/{id}.json) — no admin access required:
| Priority | Field used | Formula | Available on |
|---|---|---|---|
| 1 (preferred) | remaining_hours |
remaining_hours − new_hours |
Redmine instances with a budget/planning plugin |
| 2 (fallback) | estimated_hours + spent_hours |
estimated_hours − spent_hours − new_hours |
All standard Redmine instances |
Both strategies clamp the result to 0.0 — ETC cannot be negative.
The ETC and budget are submitted directly on the time entry (POST /time_entries.json), which is how the Redmine Budget plugin tracks them. The remaining_hours field on the issue is then automatically recalculated by the plugin — no separate write to the issue is needed. If your Redmine instance does not expose these fields, they are simply omitted from the payload — the time entry is still created successfully.
No configuration needed
The ETC update is fully automatic and requires no changes to projects.toml. It activates whenever the issue returns usable time fields, and degrades gracefully otherwise.
Verify your instance exposes the fields by running:
|
If remaining_hours appears in the output, Strategy 1 is used. If only estimated_hours and spent_hours appear, Strategy 2 (fallback) is used.
API Token
The Redmine personal API token follows the same secure lookup chain as the GitLab token — it is never stored in plain text:
1. REDMINE_TOKEN environment variable (if set — shared across all instances, useful for CI)
2. Native OS Keyring — keyed by Redmine URL (per-instance, multi-tenant safe)
3. Interactive CLI prompt → saved to OS Keyring under that URL's key
Because the keyring entry is keyed by URL, switching between two Redmine instances never clobbers the other's token.
Adding a New Tracker Plugin
The tracker system is designed to be extended without modifying any existing file except main.rs. To add a different tracker (Jira, Linear, …):
- Create a new crate (e.g.
gitlab-tracker-jira) and implement theTrackerProvidertrait fromgitlab-tracker-core. - In
projects.toml, users setprovider = "jira"in their[project.tracker]section —storage.rsandProjectEntryrequire no changes. - Add a
#[cfg(feature = "jira")]branch ingitlab-tracker/src/main.rsthat readsproject.tracker, deserialises theextrafields, and wires up the provider.
Required methods
Optional overrides (all have default no-op implementations)
// Badge colours for Type and Priority labels — read from your config.
// Return raw (bg, fg) string pairs; the orchestrator converts them to ratatui::Color.
// Omit to use the hard-coded fallback (dark_gray / white).
// Time-tracking support:
async
async
async
Wiring in main.rs
Add a #[cfg(feature = "my-tracker")] block that reads project.tracker, checks provider, deserialises extra into your config struct, then instantiates your provider:
let my_tracker_provider: = ;
The LabelColorMaps flow is the same for every provider:
projects.toml [project.tracker.*_colors]
└─ label_colors() in your provider ← String pairs, no ratatui dependency
└─ build_tracker_colors() ← converts to ratatui::Color (orchestrator only)
└─ TrackerLabelColors ← passed to the Inspector renderer