Einführung
Das Project Tree Plugin entstand ursprünglich 2019 gemeinsam mit Uwe Heber. Der Anlass war eine Installation eines Kunden mit mehreren tausend Projekten. Obwohl Redmine insgesamt hervorragend skaliert gab es einen klar abgegrenzten Engpass: die Projektübersichtsseite.
Diese lädt standardmäßig die gesamte Projektliste auf einmal. Das ist für die meisten Installationen unproblematisch, denn tausende aktive Projekte sind eher die Ausnahme. Wenn man jedoch in genau diese Größenordnung kommt, ist die Vollanzeige schlicht nicht mehr sinnvoll. Es ist weniger ein Performancefehler als vielmehr eine Frage der Darstellung und Datenmenge.
Um diese Lücke zu schließen, ergänzt das Plugin Redmine um eine eigene Top-Level-Seite „Projektbaum“, die Unterprojekte erst dynamisch lädt (Lazy Loading). Genau dieser Mechanismus macht die Ansicht selbst bei sehr großen Strukturen wieder schnell navigierbar.
Beim Umstieg auf Redmine 6 und Rails 7 wurde allerdings klar, dass das Plugin einmal komplett generalüberholt werden musste: modernisierte Rails-Konventionen, geänderte Asset-Pipeline, neue Sicherheitsmechanismen und strengere Controller-Schnittstellen führten zu einem notwendigen Rework.
Redmine ist ein seit vielen Jahren bewährtes, webbasiertes Open-Source-Projektmanagementsystem auf Basis von Ruby on Rails. Es kombiniert Ticketverwaltung, Projektplanung, Wikis, Dateien, Rollen- und Rechtekonzepte sowie Workflows in einer klar strukturierten und leicht verständlichen Oberfläche.
Ich selbst nutze Redmine seit 2012 professionell und betreibe es für Kunden mit sehr unterschiedlichen Hintergründen: von klassischen Bugtracking- und Entwicklungsprojekten über Service-Teams bis hin zu Qualitätsmanagementsystemen im Pharmaumfeld.
Gerade diese Bandbreite zeigt, wie flexibel Redmine ist. Ob einfache Vorgangsliste oder hochreguliertes Prozesswerk: Redmine bleibt übersichtlich, nachvollziehbar und ohne großen Schulungsaufwand bedienbar.
Diese Mischung aus Stabilität, Verständlichkeit und Erweiterbarkeit macht Redmine für viele Organisationen zu einem langfristig tragfähigen Werkzeug – und zu einer Plattform, die ich gerne erweitere und betreue.
Für Leser, die sich zusätzlich für Themen wie lokale KI-Modelle, Ticketautomatisierung und Wissensintegration interessieren, verweise ich gern auf meinen ergänzenden Artikel:
➡️ Lokale KI in der Praxis – Redmine-Ticketanalyse mit Ollama
Redmine & Rails – Architekturüberblick
Bevor wir in den eigentlichen Plugin-Code einsteigen, lohnt sich ein Blick auf das Fundament, auf dem Redmine aufbaut.
Redmine ist seit jeher eine klassische Ruby-on-Rails-Applikation. Rails selbst folgt dem MVC-Prinzip, also der Trennung zwischen Model (Daten), View (Darstellung) und Controller (Logik). Ergänzt wird das Ganze durch Convention over Configuration – Rails erwartet bestimmte Dateistrukturen und Dateinamen und kann dadurch eine erstaunlich klare und wartbare Architektur sicherstellen.
Was Redmine auszeichnet, ist die Konsequenz, mit der diese Prinzipien seit Jahren durchgehalten werden. Projekt-, Ticket-, Wiki- und Rechtemodelle sind sauber über ActiveRecord implementiert, und die REST-Endpunkte sind logisch aufgebaut und gut dokumentiert. Redmine bringt außerdem ein umfangreiches Plugin-System mit, das auf Rails-Konzepten aufsetzt statt sie zu umgehen.
Für Plugin-Entwickler bedeutet das:
- Es gibt klar definierte Einstiegspunkte.
- Erweiterungen können sich nahtlos in Menü, Views, Routing und Berechtigungen einklinken.
- Änderungen an Redmine selbst sind nicht nötig.
- Die Plugin-Struktur folgt denselben MVC-Vorgaben wie der Hauptcode.
Genau diese Klarheit ist der Grund, warum das Project-Tree-Plugin trotz tiefem Eingriff in die Navigationslogik vollständig updatesicher bleiben konnte.
Was das Plugin genau macht
Redmine zeigt Projekte üblicherweise als vollständige Liste. Das ist sinnvoll, solange man in einem Bereich arbeitet, in dem Projektzahlen überschaubar bleiben. In sehr großen Organisationen ist jedoch nicht die Menge der Projekte das Problem – Redmine kann intern problemlos damit umgehen – sondern der Umfang der Darstellung.
Das Plugin ersetzt diese Darstellung nicht, sondern ergänzt Redmine um eine zusätzliche Seite, nämlich „Projektbaum“, die direkt im Top-Menü erscheint.
Diese Seite zeigt ausschließlich die obersten Projektebenen. Unterprojekte werden erst geladen, wenn der Benutzer sie aufklappt. Zusätzlich berücksichtigt das Plugin Redmines Rechte- und Sichtbarkeitskonzept: Ein Benutzer sieht nur Projekte, für die er laut Redmine berechtigt ist.
Damit handelt es sich nicht nur um eine alternative Visualisierung, sondern um ein vollständig integriertes Werkzeug für große Projektlandschaften.
Wie ein Redmine-Plugin aufgebaut ist
Ein Redmine-Plugin folgt denselben Strukturen wie das Hauptsystem. Die wichtigsten Dateien im Projektbaum-Plugin sind:
init.rb
config/routes.rb
app/controllers/
app/views/
assets/javascripts/
assets/stylesheets/
Jede dieser Komponenten erfüllt eine klar definierte Aufgabe.
Registrierung des Plugins – init.rb
Der Einstiegspunkt eines jeden Plugins ist die Datei init.rb.
Hier meldet sich das Plugin bei Redmine an, registriert sich im Menü und hinterlegt Metadaten.
Redmine::Plugin.register :project_tree do
name 'Project Tree plugin'
author 'Uwe Heber, Matthias Petermann'
version '3.0.0'
menu :top_menu,
:project_tree,
{ controller: 'project_tree', action: 'index' },
caption: :label_project_tree,
after: :projects
end
Was hier passiert, ist bemerkenswert wenig – und genau das ist die Stärke: Ein einzelner Menüeintrag genügt, um eine neue Top-Level-Seite einzufügen.
Routing – wie der Browser zum Controller findet
Redmine nutzt das standardisierte Rails-Routing. Plugins können eigene Routen definieren, ohne die bestehenden zu überschreiben.
get 'project_tree', to: 'project_tree#index'
get 'projects/:id/children', to: 'project_tree#children'
Die erste Route liefert die Baumansicht selbst. Die zweite Route liefert die Unterprojekte als JSON-Antwort. Der Browser ruft diese Route auf, sobald der Benutzer einen Projektknoten ausklappt.
Controller – Daten laden und Rechte prüfen
Der Controller verbindet Datenmodell und Darstellung. Im Plugin steckt die vollständige Backend-Logik in einer einzigen Klasse:
class ProjectTreeController < ApplicationController
before_action :require_login
def index
@top_projects =
Project.visible(User.current)
.where(parent_id: nil, status: 1)
.order(:name)
end
def children
project = Project.visible(User.current).find(params[:id])
children =
project.children.visible(User.current)
.where(status: 1).order(:name)
render json: {
children: children.map { |c|
{ id: c.id, name: c.name, identifier: c.identifier,
has_children: c.children.visible(User.current).exists? }
}
}
end
end
Diese Logik ist bewusst schlank gehalten: Redmine stellt die Werkzeuge bereit, um Sichtbarkeit und Rechte sicher zu bestimmen. Das Plugin nutzt diese Mechanismen statt eigene Filter zu erfinden.
View – HTML-Darstellung der Projektwurzel
Die View ist eine ERB-Datei und erzeugt die HTML-Struktur des Baumes.
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'project_tree', plugin: 'project_tree' %>
<%= javascript_include_tag 'project_tree', plugin: 'project_tree' %>
<script>window.projectTreeIconsUrl = "<%= asset_path('icons.svg') %>";</script>
<% end %>
<ul class="tree">
<% @top_projects.each do |project| %>
<% if project.children.any? %>
<li class="project root parent">
<span class="toggle collapsed"
data-id="<%= project.id %>">
<svg class="s18 icon-svg">
<use href="<%= asset_path('icons.svg') %>#icon--angle-right"></use>
</svg>
</span>
<%= link_to_project(project) %>
</li>
<% else %>
<li class="project root parent">
<span class="spacer"></span>
<%= link_to_project(project) %>
</li>
<% end %>
<% end %>
</ul>
Die View selbst enthält keine Logik, sondern nur Darstellung. Das eigentliche Verhalten entsteht im Browser mittels JavaScript.
JavaScript – dynamisches Nachladen der Unterprojekte
Das Plugin setzt auf jQuery, da Redmine es selbst mitbringt und Plugins möglichst kompatibel bleiben sollten.
$(document).ready(function () {
$("span.toggle").on("click", function () {
showHideChildren($(this));
});
});
function showHideChildren(toggle) {
if (toggle.hasClass("collapsed")) {
toggle.addClass("expanded").removeClass("collapsed");
addChildren(toggle);
} else {
toggle.addClass("collapsed").removeClass("expanded");
removeChildren(toggle);
}
}
Beim Aufklappen lädt das Plugin die Unterprojekte nach und fügt sie dynamisch in die Baumstruktur ein. Dieses Lazy Loading ist der Kern der guten Performance – der Browser verarbeitet immer nur die gerade benötigte Teilmenge.
Fazit
Redmine ist in seiner Architektur bemerkenswert robust. Es skaliert zuverlässig, es bleibt gut wartbar, und durch die klaren Rails-Wurzeln ist es ausgezeichnet erweiterbar.
Das Project-Tree-Plugin zeigt exemplarisch, wie man Redmine erweitern kann, ohne es zu verbiegen. Es ergänzt Redmine dort, wo die Standardansicht bei sehr großen Installationen unpraktisch wird – und tut das, ohne bestehende Mechanismen zu überschreiben.
Eine schlanke Erweiterung, sauber integriert und vollständig updatesicher.
Offizielle Seiten
Projektbaum-Plugin (Source Code)
Bildnachweis: Das Redmine-Logo © 2009 Martin Herr, lizenziert unter CC BY-SA 2.5 (http://creativecommons.org/licenses/by-sa/2.5/). Das Ruby-on-Rails-Logo ist eine Marke des Rails Core Teams und wird im Rahmen redaktioneller Berichterstattung verwendet.