Projektbaum für Redmine 6: Ein notwendiger Rework

Projektbaum für Redmine 6: Ein notwendiger Rework
By Matthias Petermann / on 22.11.2025

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.

ℹ️ Was genau ist Redmine?

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.

💡 Weiterführender Beitrag

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.

ℹ️ Ressourcen & Links

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.