ALM mit Redmine, Teil 2

Im ersten Teil hatte ich ja bereits angekündigt, dass ich über unsere Erfahrungen mit Redmine weiter berichten werde. Leider hat sich das Ganze anders entwickelt als gedacht. Da bei uns vornehmlich Outlook zum Einsatz kommt und wir auch alle unsere Kundendaten im Outlook haben, nutzen wir jetzt doch eine Kommerzielle Erweiterung für Outlook um unsere Support-Anfragen zu bearbeiten. Aber nichts des do trotz arbeite ich jetzt erst mal alleine mit Redmine.

Da ich mehrere kleine und große Projekte habe, die ich in Eigenregie plane und Entwickle ist eine gute Aufgabenplanung unerlässlich. Mit Redmine habe ich jetzt endlich ein vernünftiges Werkzeug dafür gefunden. Ich habe mehrere Projekte und Unterprojekte in denen vor allem die drei Tracker “Bug”,  “Feature” und “Anforderung” zum Einsatz kommen. Für Bugs war das ganze ja sowieso gedacht, jetzt kommt der Bugtracker halt zum Einsatz wenn ich selber Fehler finde oder der Kunde mir direkt welche schickt. Im Tracker “Feature” landen Ideen für Erweiterungen unserer Produkte.

Besonders wichtig ist für mich in letzter Zeit der Tracker “Anforderung” geworden. Da es sich hier um konkrete Aufgaben handelt, welche für den Kunden abgearbeitet werden müssen, ist das der Tracker mit den meisten Tickets. Außerdem nutze ich den Tracker dafür, meine Aufwände zu Planen. Ich zerlege die Aufgaben so, dass ich bei Maximal ein bis zwei Tagen, am besten jedoch bei ein bis zwei Stunden rauskomme. Somit kann ich bei einem Angebot ziemlich zuverlässig sagen wie viel Manntage bezahlt werden müssen.

Damit die Planung auch Sinnvoll und Realistisch ist, muss natürlich der tatsächliche Aufwand mit erfasst werden. Erst dadurch ist es möglich die Abschätzungen zu überprüfen und bei neuen Aufgaben genauere Zahlen zu liefern. Damit man nicht ständig auf die Uhr schauen muss wenn man an einer Aufgabe arbeitet, gibt es zum Glück das TimeTracker Plugin. Es zeigt in der rechten oberen Ecke einen Button “Start #xxx” mit dem die Zeiterfassung für die aktuelle Aufgabe begonnen werden kann. Wenn der Timer läuft wird an dieser Stelle immer die aktuell benötigte Zeit angezeigt. Und da es wahrscheinlich vielen so geht wie mir, die beim Arbeiten durch Telefonate oder Sonstiges abgehalten werden, gibt es einen Pause-Button, welcher die Zeitmessung unterbricht. Drück man schließlich auf den Stopp-Button öffnet sich automatisch die Aufwands-Erfassungsmaske, in der die benötigte Zeit, in Stunden umgerechnet, bereits eingetragen ist. Hier kann man nun noch ein Kommentar mit einer Beschreibung der Tätigkeit eingeben und das Ganze als Tätigkeit für die Aufgabe speichern.

Da bei mir der Berg an Papier mit Feature-Wünschen und Anpassungen von unseren Kunden immer weiter gewachsen ist und ich da nicht mehr wirklich durchgesehen habe, wird so etwas jetzt immer eingescannt. Die PDF Datei hänge ich dann an die konkrete Aufgabe oder den Fehler als Anlage mit dran. Somit entfällt das lange Gesuche nach irgendwelchen Blättern mir Designvorgaben und Co. Das gleiche gilt natürlich auch für Dokumente oder Screenshots die ich per Mail erhalten habe. Sollte solch eine Datei mehrere Aufgaben betreffen, gibt es in Redmine die Möglichkeit, Dokumente zu einem Projekt zu speichern. Diesen Dokumenten gibt man einen Namen und eine kurze Beschreibung. Per document#xxx (xxx ist die ID des Dokuments) kann man dann schließlich in der Beschreibung einer Aufgabe oder einem Kommentar auf das Dokument verlinken. Hierbei wird der Ausdruck mit dem Dokumentnamen ersetzt. Es gibt übrigens eine ganze Reihe solcher Ausdrücke , um zwischen Dokumenten, Wiki-Einträgen und anderen Aufgaben zu verlinken. Eine Übersicht dafür liefert die Wiki-Seite von Redmine.

Advertisements

Hinterlasse einen Kommentar

jQuery Tablesorter Pager: Jump to Page

This is my first blog in english, so sorry for any spelling errors.

In one of our projects, we use the great jQuery library. A very cool plugin for jQuery is the tablesorter and the extsion to this, the pager. The only disadvantage of the pager plugin is the missing function “Jump To Page”. You can only display the current page and the page count as one string. So i have modified the code of the pager plugin a little, to add this function.

It is a very simple extension, only two existing functions need to be updated. To identify the input-field for the current page and the div for the pagecount, i added two new properties to the settings: cssPageCurrent and cssPageCount. The first function to update is the constructur. Here i connect to the change-event of the input-field and call the moveToPage function. The second function is named updatePageDisplay. It is responsible to update the number in the currentpage-inputfield if you navigate with the next/prev buttons.

Here are the updated functions:

function updatePageDisplay(c, table) {
    var txt = (c.page + 1) + c.seperator + c.totalPages;
    var pd = $(c.cssPageDisplay, c.container)
    if (pd.is("input"))
        pd.val(txt);
    else
        pd.text(txt);

    var pcur = $(c.cssPageCurrent, c.container)
    if (pcur.is("input"))
        pcur.val(c.page + 1);
    else
        pcur.text(c.page + 1);

    var pcount = $(c.cssPageCount, c.container)
    if (pcount.is("input"))
        pccount.val(c.totalPages);
    else
        pcount.text(c.totalPages);

    $(table).trigger("tableSorterPagerUpdatePageDisplay");
}

this.defaults = {
    size: 10,
    offset: 0,
    page: 0,
    totalRows: 0,
    totalPages: 0,
    container: null,
    cssNext: '.next',
    cssPrev: '.prev',
    cssFirst: '.first',
    cssLast: '.last',
    cssPageDisplay: '.pagedisplay',
    cssPageSize: '.pagesize',
    cssPageCount: '.pagecount',
    cssPageCurrent: '.pagecurrent',
    seperator: "/",
    positionFixed: true,
    appender: this.appender
};

this.construct = function (settings) {

    return this.each(function () {

        ...

        $(config.cssPageCurrent, pager).change(function () {
            var c = table.config;
            var p = $(this).val();

            if (p != c.page + 1) {
                p = parseInt(p) - 1;
                if (p < 0 || isNaN(p)) p = 0;
                else if (p > c.totalPages - 1) p = c.totalPages - 1;

                c.page = p;
                moveToPage(table);
            }

            return false;
        });
    });
};

Here you can download the changed scriptfile: jquery.tablesorter.pager.js

And here is how i used it:

<html>
<head>
    <script type="text/javascript">
        $(document).ready(function () {
            ...
            $(".tblSortable").tablesorter();
            $(".tblSortable").tablesorterPager({ container: $("#pager"), positionFixed: false, size: 30 });
            ...
        });
    </script>
</head>
<body>
    ...
    <div id="pager" align="center">
        <input type="text" name="pagecurrent" class="pagecurrent">
        <span> / </span>
        <span class="pagecount">1</span>
    </div>
    ...   
    <table class="tblSortable">
        ...
    </table>      
</body>
</html>

,

2 Kommentare

ALM mit Redmine, Teil 1

Kürzlich war ich auf der Suche nach einem guten Bugtracker, um den Support in unserer Firma etwas zu unterstützen und um so eine Art Wissensdatenbank aufzubauen. Zur Zeit werden bei uns Fehlermeldungen und Probleme von Kunden unserer Produkte vom Support-Mitarbeiter entgegengenommen und so weit geholfen wie möglich. Wenn unser Support-Mitarbeiter nicht mehr weiter weiß, wird das Problem telefonisch oder per E-Mail an den entsprechenden Entwickler dieser Software weitergeleitet. Der bearbeitet dann das Ganze und schickt dem Kunden eine Lösung bzw. verweist ihn auf ein kommendes Update der Software. Es ist natürlich ungünstig für den Entwickler, wenn ähnliche Probleme immer wieder bei ihm auf den Tisch landen und ihn von der eigentlichen Arbeit abhalten.

Um diesen Missstand zu beheben wollen wir jetzt einen Bugtracker einführen welcher den Support-Mitarbeiter bei der Fehlerbehebung unterstützt. Einmal aufgetauchte Fehler oder auch nur Anfragen werden in den Bugtracker eingetragen und kategorisiert. Wenn der Fehler nicht gleich behoben werden kann, kümmert sich der entsprechende Entwickler darum und trägt die Lösung ein bzw. verweist auf die Programmversion, in der der Fehler behoben ist. So weit der Plan jedenfalls!

Bei meiner Suche nach entsprechenden Lösungen bin ich natürlich auf tausende von Systemen und Programme gestoßen. Da das Ganze aber so kostengünstig wie möglich sein soll, blieben nur ein paar wenige Open-Source Programme übrig. Die da wären Bugzilla, Mantis, Trac und schließlich Redmine.

Von den ganzen Programmen hat mir Redmine am besten gefallen. Bugzilla war zwar gut, aber die Installation ist einfach unmöglich. Mantis ist vom Umfang her auch recht gut, aber die Oberfläche sieht einfach altbacken aus. Und Trac schließlich hat keine wirklich zufriedenstellende Multi-Projekt-Unterstützung und eine zu komplizierte Oberfläche. Redmine war mir vorher total unbekannt, aber dank Bitnami und ihren fertigen All-in-one-Installern bzw. ihren fertigen Virtuellen Maschinen konnte ich den Bugtracker ohne Probleme ausprobieren.

Also läuft jetzt bei uns erst mal eine VM mit Ubuntu 10.10, Apache, MySql, Ruby und Redmine zum Testeinsatz. Dank der Weboberfläche war es das schon fast mit der Installation der Software im Unternehmen. Jetzt bracht nur noch jeder Mitarbeiter ein Login und den Link mit der IP der VM und dem Programm. Die Weitere Einrichtung des Programmes sowie unsere Erfahrungen damit, werden in den kommenden Teilen beschrieben.

, , ,

Hinterlasse einen Kommentar

gemanagte Referenzen in einer nicht gemanagten Klasse speichern

Wenn man C++ CLI und normales C++ in einem Projekt zusammen einsetzt, tauchen früher oder später scheinbar unlösbare Probleme auf. Bei einem Videoplayer den wir zur Zeit programmieren, war es nun soweit.

Um den Programmcode etwas zu entschlacken und den darunter liegenden Algorithmus zur Erstellung eines DirectShow Aufnahmegraphen wieder sichtbar zu machen, wurde es endlich Zeit eine Referenz auf ein gemanagtes Objekt in einer nativen Klasse zu speichern.

Im WPF-Frontend (C#) stellt der Nutzer ein, von welcher Kamera er aufnehmen will und in welches Format das geschehen soll. Diese Konfiguration wird an das Backend (C++) übergeben, in dem schließlich der Aufnahmegraph aufgebaut wird. Dabei kann es sehr viele unterschiedliche Kombination der Konfigurationsparameter geben. Die Art der Kamera (DV, USB, …), die Audioquelle, TimeShift (darüber werde ich bei Gelegenheit auch noch einen Blog-Eintrag schreiben => das war meine Diplomarbeit!) und noch einige mehr spielen hierbei eine Rolle.

Der Aufbau des Aufnahmegraphen erfolgte in einer großen Klasse mit etlichen Funktionen, und immer wieder wurde als Parameter dieser Funktionen ein Parameter vom Typ ICaptureConfiguration^ übergeben. Also habe ich das ganze aufgeflochten und in mehrere Klassen verteilt. Eine abstrakte Basisklasse mit statischen Hilfsfunktionen und einem Interface mit den wichtigsten Methoden, welche dann durch die Detail-Klassen abgedeckt sein müssen. Und um nicht ständig die Konfiguration als Parameter übergeben zu müssen, wird dieser Kontraktur als Member gespeichert.

Doch oh Schreck, was ist das: “Der Typ ICaptureConfiguration^ kann nicht als Membervariable einer nicht C++ CLI Klasse verwendet werden!” Ist ja eigentlich auch logisch, wie soll sonst der Speichermanager der CLR mitbekommen, wann die Referenz nicht mehr gebraucht wird und er das Objekt wieder freigeben kann. Oder noch schlimmer, wenn der Speichermanager des Objekt in seinem Speicher an eine andere Adresse schiebt um Platz zu schaffen, wäre die Referenz plötzlich ungültig! Es gibt nun nur zwei Möglichkeiten: entweder alles auf C++ CLI umstellen, was mindesten 100 Tage oder mehr dauern würde oder aber doch einen Weg finden, die Referenz zu speichern.

Dank google, der MSDN und anderen Seiten ist es mir zum Glück schließlich doch gelungen die Referenz zu speichern. Genau dafür gibt es nämlich das Template gcroot<T>. Es sorgt dafür, dass die zugewiesene Referenz immer erreichbar ist. Da man solch eine Referenz immer mit delete wieder freigeben müsste, ist es besser man nutzt auto_gcroot<T>. Das wird nämlich wie alle Wrapper dieser Art automatisch freigegeben. Im Code sieht das ganze dann in etwa so aus:

#include <msclr\auto_gcroot.h>
#using <mscorlib.dll>

using namespace System;
...

class CCaptureMgrBase
{
    CCaptureMgrBase(ICaptureConfiguration^ captureConfig)
    {
        m_captureConfig = captureConfig;
    }
    ...
    msclr::auto_gcroot<ICaptureConfiguration^> m_captureConfig; 
}

Mit –> kann man nun ganz normal auf die Referenz zugreifen und Funktionen aufrufen.

Eine kleine Anmerkung habe ich allerdings noch. Man kann m_captureConfig nicht mehr einfach so an eine Funktion übergeben, welche ein ICaptureConfiguration^ als Parameter erwarte! Dafür muss man nun m_captureConfig.get() aufrufen.

class CCaptureMgrBase
{
    ...
    void Render()
    {
        // geht nicht:
        CCaptureMgrBase::Helper(m_captureConfig);

        // so ist es richtig
        CCaptureMgrBase::Helper(m_captureConfig.get());
    }

    static void Helper(ICaptureConfiguration^ captureConfig) { ... }
}

,

Hinterlasse einen Kommentar

(Nerd-)Dinner for One MVC Framework

Bei der Suche nach einem guten Framework für die Erstellung von zwei Onlineportalen auf Asp.Net Basis bin ich auf das Asp.Net MVC Framework gestoßen. Ich hatte zwar vorher schon mal davon gelesen und gehört, es aber immer wieder abgetan als “Noch eines dieser sinnlosen Frameworks”.

Model-View-Controller Frameworks sollten jedem Web-Entwickler ein Begriff sein. Angefangen von Ruby on Rails, über die verschiedenen PHP Frameworks wie das Zend Framework, bis schließlich zu den Java Bibliotheken von Struts und Spring, gab es lange Zeit keine vernünftige MVC Unterstützung für die .Net Welt. Dank Scott Guthrie und dem MonoRail Projektes gibt es das nun, und zwar mit dem, seit 2009 unter einer Open Source Lizenz stehenden, Asp.Net MVC Framework.

Ich muss sagen ich bin wirklich begeistert von dem Asp.Net MVC Framework. Ich kann jedem nur den Einführungs-Webcast von dem immer zu Witzen aufgelegten Scott Hanselman empfehlen. Als Referenz für ein eigenes Projekt oder einfach als Einstig in die verschiedene Aspekte des Asp.Net MVC Frameworks dient das Open Source Projekt NerdDinner.com. Passend dazu ist seit kurzem auch des Einführungskapitel des Buches “Professional ASP.NET MVC 2.0” unter Creative Commons freigegeben wurden.

Mehr zu dem Framework und meinen Erfahrungen damit in einem der nächsten Blog-Einträge. Somit erst mal guten rutsch ins neue Jahr.

Hinterlasse einen Kommentar

“Hallo (Blog-)Welt”

Wie es sich bei dem Einstieg in eine neue (Programmier-)Welt gehört, muss diese auch ordentlich begrüßt werden. Da darf das obligatorische “Hallo Welt” natürlich nicht fehlen. In diesem Fall die Welt des Bloggens.

Angestachelt durch das Video „Social Networking for Developers and Making Your Blog Suck Less“ von Scott Hanselman und natürlich durch seinen Blog, habe ich mich dazu  entschlossen, es doch nun auch einmal mit bloggen zu versuchen. Und was liegt da natürlich näher als ein Blog über das was ich tag-täglich mache: Programmieren!

Wie man aus dem Titel des Blogs schon entnehmen kann wird es vorallem um C++ und C# gehen, da das die zwei Sprachen sind mit denen ich am meisten zu tun habe. Und passend dazu natürlich um das .Net Framework (ab Version 2, alles davor war nur Beta). Der Titel soll den Blog aber nicht zu weit einschränken. Ich hab auch sehr viel mit XML und XSLT sowie HTML, JavaScript und CSS zu tun. Und seit kurzem, dank Android, auch wieder mit Java.

Man sieht also schon, es wird eine Bunte Mischung werden. Bei der ich aber hoffentlich den Roten Faden immer im Auge habe, hier mein Wissen, meine Erfahrung und meine Gedanken zusammen zufassen.

Hinterlasse einen Kommentar