JIRA plugins en real-time events

Voor het ondersteunen van ons interne software ontwikkelingsproces heeft Cyso lange tijd Trac gebruikt. Trac is een eenvoudig issue management systeem, en voldoet prima voor kleine projecten. Naast Trac gebruiken we Subversion voor het beheren van onze broncode en het uitrollen (releasen) van nieuwe software versies.

Versiebeheer

Wat is versiebeheer? Met versiebeheer is het mogelijk om voor een bepaald project alle wijzigingen die gemaakt zijn te beheren. Hierdoor is het mogelijk om later altijd terug te kunnen vallen op een oudere versie, of om nog eens na te kijken wie een bepaalde wijziging gemaakt heeft. Bij de meeste versiebeheersystemen worden alle revisies voor eeuwig bewaard, en is het dus niet mogelijk om oude revisies te verwijderen. Op dit moment zijn de meest gebruikte versiebeheersysteem SVN (Subversion) en Git.


Subversion

Subversion is een voorbeeld van een versiebeheersysteem met een centrale aanpak. Alle code staat opgeslagen op een centrale server, en developers kunnen een ‘check-out’ maken, zodat er lokaal gewerkt kan worden aan de code. Wijzigingen worden via een ‘commit’ teruggestuurd naar de server, vergezeld door een ‘commit-message’, waar veelal een beschrijving in staat van het doel van de wijzigingen, en vaak ook een verwijzing naar een ticket. Een commit zorgt ervoor dat er een nieuwe revisie aangemaakt wordt, met daarin de wijzigingen en de commit-message. Alleen de server beschikt over alle historie, de check-out bevat alleen de bestanden zoals ze waren in een bepaalde revisie. Als er door de developer een andere revisie opgevraagd wordt, zal Subversion deze bij de server opvragen, en de check-out van de developer veranderen naar de staat zoals hij was tijdens de opgevraagde revisie.

Git

Git is een voorbeeld van een decentraal versiebeheersysteem. Bij deze aanpak is het geen centrale server, een developer beschikt in zijn lokale check-out over de volledige historie. Een voordeel van deze aanpak is dat er doorgewerkt kan worden, ook als er geen netwerk beschikbaar is. Tevens is deze aanpak ook wat flexibeler en lichter, aangezien er niet per definitie een centrale server opgezet hoeft te worden. Een developer kan met Git een lokale check-out starten, en committen naar deze check-out. Als er dan later alsnog de wens is om code uit te wisselen met andere developers, kan er gekozen worden om een check-out te maken op een centrale plek. Een developer kunnen dan ‘pushen’ naar deze centrale repository, waarna andere developers kunnen ‘pullen’, en zo informatie en revisies kunnen uitwisselen. Dit centrale punt noemt men de ‘canonical origin’.

 

Trac


Trac
omgeving is gekoppeld aan maximaal één Subversion repository. Via deze koppeling is het mogelijk om tickets in Trac te veranderen, updaten en sluiten, allemaal via commit messages die gedaan worden op de repository. Daarnaast wordt er bijvoorbeeld ook integriteit afgedwongen door de repository. Zo wordt elke commit die gedaan wordt naar de repository eerst gecheckt op syntax fouten en andere problemen. Indien problemen geconstateerd worden, zal de commit mislukken, en zal de ontwikkelaar zijn code moeten verbeteren voordat de repository de commit accepteert. Dit zorgt ervoor dat het niet mogelijk is om code uit te rollen naar live systemen waar fouten in voorkomen die vooraf vermeden hadden kunnen worden.

Dit systeem voldeed prima voor Cyso. Echter, met de start van het CysoAPI en Service project, werd al snel duidelijk dat vooral de één-op-één koppeling tussen Trac en SVN een probleem opleverde. Aangezien tickets vaak zowel te maken hebben met zowel de CysoAPI als Service, is het standaard niet mogelijk om de tickets centraal te houden. Een ticket heeft immers betrekking op slechts één repository. Dit komt erop neer dat tickets die over meerdere projecten gaan meerdere keren ingeschoten moeten worden, en apart geüpdatet moeten worden in elke Trac omgeving.

Het bovenstaande hebben we met wat kunst- en vliegwerk kunnen vermijden door de updates die gedaan worden via de CysoAPI Subversion repository in de Service Trac terecht te laten komen. Hierdoor is het dus mogelijk om commits voor de CysoAPI terug te laten komen in tickets voor de ServiceV3 Trac. Een commit message als:

view.thtml – tabellen omgezet naar divs, refs #234

Zorgt ervoor dat ticket #234 geupdate wordt, en dat er een comment wordt toegevoegd met de inhoud van de commit message. Voor Trac is het echter nog steeds alsof er maar één repository is, die van Service.

Lang verhaal kort: Trac is een prima en flexibel systeem. Cyso is dit systeem in de laatste jaren ontgroeid, door grote en complexe projecten.

Atlassian

De zoektocht was dus begonnen naar een vervangend systeem voor Trac. Tijdens het zoeken hebben we voornamelijk gekeken naar een open-source oplossing, zoals Trac dat ook was. Al snel werd duidelijk dat veel van de open-source issue trackers ook een één-op-één afdwingen met de repository. Uiteindelijk zijn we uitgekomen op de tools van Atlassian.

Atlassian biedt een complete toolkit, bestaande uit een issue tracker (JIRA), een wiki (Confluence), een single-signon systeem (Crowd) en een repository viewer (Fisheye). Het grote voordeel van deze oplossing is dat alle tools met elkaar te integreren zijn. Zo is het bijvoorbeeld mogelijk om in een wiki artikel in Confluence een real-time overzicht op te nemen van tickets uit een JIRA project. Hiermee kunnen de oude lijstjes met actiepunten eindelijk vaarwel gezegd worden. Daarnaast is het bijvoorbeeld mogelijk om je eigen Dashboard in elkaar te slepen binnen JIRA, zodat je een persoonlijk en relevant overzicht krijgt van openstaande issues, de deadline voor de volgende release, activiteit van andere gebruikers in JIRA en Confluence, en nog veel meer.

Eén groot nadeel wat ons snel duidelijk werd na het overstappen, is dat het beïnvloeden van tickets vanuit commit messages niet direct meer mogelijk is. Er kan nog steeds gerefereerd worden naar een ticket, door een ticket-nummer op te nemen in de message. Fisheye zal dit oppikken, en doorgeven aan JIRA, waardoor de commit message aan het ticket gekoppeld is, en daar terug te vinden is. Met Trac was het echter ook mogelijk om een ticket direct te sluiten, met een commit message als:

controller.php – belangrijke bugfix voor het X probleem, closes #123

De bovenstaande message zorgde ervoor dat de message gekoppeld werd aan ticket #123, en dat ook meteen het ticket gesloten werd, door middel van het ‘closes’ keyword. In JIRA is het bovenstaande niet standaard mogelijk.

JIRA Listeners

Gelukkig is het mogelijk om een eigen plugin te ontwikkelen voor JIRA. Via deze plugin is het mogelijk om bijvoorbeeld events op te vangen die zich afspelen binnen JIRA, zoals het aanmaken of updaten van een ticket. Door hier een zogenaamde Listener voor te schrijven, kunnen deze verwerkt worden op elke gewenste manier, omdat je de hele Java en Atlassian SDK toolkit tot je beschikking hebt.

Om ons ontwikkelingsproces wat interactiever te maken, hangt in het development kantoor een scherm, met daarop de laatste activiteit op het gebied van tickets en commits. Dit systeem, genaamd InfoStorm, is een informatie aggregator, en verzamelt deze informatie uit verschillende bronnen. Bij Trac gebruiken we hier de uiterst geschikte RSS feed voor, door deze elke 20 seconden te pollen. Elke update die hieruit gedestilleerd wordt, wordt via InfoStorm naar elke verbonden client gepusht, zoals het eerder genoemde scherm in het development kantoor. De RSS feed van JIRA bevat echter veel meer loze informatie, zoals links om direct naar het edit scherm van een geüpdatet ticket te gaan. Daarnaast genereert JIRA deze RSS feeds slechts periodiek, waardoor updates pas laat zichtbaar zijn op het scherm.

Om de bovenstaande problemen te tackelen, is de beste oplossing om hiervoor een Listener voor JIRA te schrijven. Aangezien de interne events wel real-time plaatsvinden, is het mogelijk om informatie veel sneller door te spelen naar InfoStorm dan hiervoor mogelijk met de RSS feed. Een update in JIRA zal dus direct zichtbaar zijn in InfoStorm.

Ontwikkelen

Atlassian heeft een goede handleiding beschikbaar waarin staat uitgelegd hoe je een ontwikkelomgeving op kan zetten. Het komt erop neer dat Maven gedownload wordt, en een aantal handige Atlassian tools. Met deze tools is het mogelijk om een skelet te genereren afhankelijk wat je wilt maken. In mijn geval is dit een JIRA plugin, dus met het commando `atlas-create-jira-plugin` wordt er voor mij een skelet voorbereid voor het ontwikkelen. Via Maven worden alle benodigde libraries gedownload, inclusief een JIRA development server die je kan draaien op je ontwikkelbak. Als het skelet eenmaal aangemaakt is, kan je in je IDE (IntelliJ en Eclipse worden ondersteund) een project maken. Dit kan op basis van de gegenereerde Maven .pom, of met een commando wat een Eclipse project aanmaakt.

Hierna heb je beschikking over een volledig ingesteld project, en kan het ontwikkelen beginnen. Standaard is er een “Prints events to System.out with Parameters” Listener meegeleverd, welke een goed startpunt biedt voor het ontdekken wat voor informatie een Event bevat. Ik heb in de documentatie van Atlassian geen goede bron kunnen vinden over de informatie die een Event kan en zal bevatten, dus een groot deel heb ik via reverse-engineering uit moeten vinden. De documentatie bevat wel de methodes die de verschillende objecten bevatten, maar geen informatie over wat de data betekent.

Verbinden met InfoStorm

Zoals gezegd is het de bedoeling dat de te ontwikkelen Listener een verbinding gaat opzetten met InfoStorm, om zo informatie door te spelen. Aangezien beide kanten gebaseerd zijn op Java, zal ik kiezen voor een ObjectStream om een compleet over te sturen. Op deze manier vermijd ik het omzetten van de data naar een ander formaat zoals XML of JSON.

De eerste stap is het destilleren van alle beschikbare data uit de Event, en bepalen wat we willen bewaren en oversturen. Aangezien we objecten gaan sturen, zal dit een class moeten worden:

public class EventMessage implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id = null;
    private long event_type = EventType.UNKNOWN;
    private String assignee = null;
    private String reporter = null;
    private Timestamp timestamp = null;

    private String summary = null;
    private String ticket_type = null;
    private String environment = null;
    private String description = null;
    private String comment = null;
    private Long estimate = 0L;
...

}

Ik heb de constructors en getters/setters weggelaten, aangezien deze niet interessant zijn. Tevens heb ik een extra class gedefinieerd met daarin de constantes die door JIRA gebruikt worden om event types te onderscheiden. Standaard is daar al een class voor (com.atlassian.jira.event.type.EventType), maar die definieert de constantes als een Long object, in plaats van de long primitive. Dit zou geen probleem moeten zijn, ware het niet dat Event.getEventType() soms een Long object, en soms een long primitive teruggeeft. Om dit goed op te vangen moet ik met beide situaties rekening houden.

Ik heb dit opgelost door de onderstaande nieuwe class aan te maken:

public final class EventType implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final long UNKNOWN = 0L;

    public static final long ISSUE_ASSIGNED = 3L;
    public static final long ISSUE_CLOSED = 5L;
    public static final long ISSUE_COMMENT_EDITED = 14L;
    public static final long ISSUE_COMMENTED = 6L;
    public static final long ISSUE_CREATED = 1L;
    public static final long ISSUE_DELETED = 8L;
    public static final long ISSUE_GENERICEVENT = 13L;
    public static final long ISSUE_MOVED = 9L;
    public static final long ISSUE_REOPENED = 7L;
    public static final long ISSUE_RESOLVED = 4L;
    public static final long ISSUE_UPDATED = 2L;
    public static final long ISSUE_WORKLOG_DELETED = 16L;
    public static final long ISSUE_WORKLOG_UPDATED = 15L;
    public static final long ISSUE_WORKLOGGED = 10L;
    public static final long ISSUE_WORKSTARTED = 11L;
    public static final long ISSUE_WORKSTOPPED = 12L;

    public static final boolean isValidEventType(long type) {
        if (type == ISSUE_ASSIGNED || type == ISSUE_CLOSED || type == ISSUE_COMMENT_EDITED
         || type == ISSUE_COMMENTED || type == ISSUE_CREATED || type == ISSUE_DELETED
         || type == ISSUE_GENERICEVENT || type == ISSUE_MOVED || type == ISSUE_REOPENED
         || type == ISSUE_RESOLVED || type == ISSUE_UPDATED
         || type == ISSUE_WORKLOG_DELETED || type == ISSUE_WORKLOG_UPDATED
         || type == ISSUE_WORKLOGGED || type == ISSUE_WORKSTARTED
         || type == ISSUE_WORKSTOPPED) {
            return true;
        } else {
            return false;
        }
    }
}

Deze class, met de (vieze) functie isValidEventType vangt alle bovenstaande problemen op, en heeft als voordeel dat ik op deze manier later simpel nieuwe EventTypes kan toevoegen als ik bijvoorbeeld voor Fisheye of Confluence een vergelijkbare plugin wil schrijven.

De laatste stap is het werkelijk uitrollen van de plugin. Op JIRA kan dit simpelweg gedaan worden door het volgende uit te voeren in je workspace:

$ atlas-compile
$ atlas-package

Je houdt dan een .jar bestand over, met daarin je plugin. Deze moet je vervolgens uploaden naar de server waar JIRA op draait, en hem dan in de /home/<jira-user>/plugins/installed-plugins deponeren. Vervolgens dient JIRA herstart te worden om de plugin ook werkelijk te laden. Tijdens het opstarten is het handig om log/catalina.out in de gaten te houden, aangezien je hier direct foutmeldingen kan terugvinden als het laden van de plugin niet goed gaat. Als het wel goed gaat, zal je een melding kunnen verwachten in de trant van:

[atlassian.plugin.loaders.ScanningPluginLoader] Plugin Unit: /home/jira/plugins/installed-plugins/InfoStormListener-1.0-master.jar (1275398014000) created

Hierna dien je als JIRA Administrator nog een Listener toe te voegen aan de configuratie. Dit kan via de webinterface:

 

Zodra dit gedaan is, zullen alle Events die gegenereerd worden door acties in JIRA ook gestuurd worden naar de Listener die we net gedefinieerd hebben. Dit heeft dus als gevolg dat elke actie uit JIRA die een Event veroorzaakt, in real-time wordt doorgestuurd naar InfoStorm. Vanaf dit punt wordt het ook real-time doorgestuurd naar elke client die verbonden is met InfoStorm. De eerste situatie met een RSS feed die gepolled werd voor nieuwe content wordt op deze manier compleet vervangen door een veel nettere, aanpasbare, maar voor veel snellere methode.





Door Nick op 7 juni 2010.
Ontwikkeling.

 

Both comments and pings are currently closed.

 

Over de auteur


Het bovenstaande artikel is geschreven op 7 juni 2010 door Nick Douma:

Ik ben werkzaam bij Cyso als Software Architect. Mijn dagelijkse bezigheden bestaan uit het ontwerpen van nieuwe (software)systemen voor Cyso, het beheren van de source code repositories van Cyso en projectmatig werken aan verschillende projecten binnen Cyso. Daarnaast doe ik onderzoek naar nieuwe technieken voor nieuwe projecten of het verbeteren van huidige werkzaamheden.

Klik hier voor een overzicht van artikelen die door Nick geschreven zijn.