Wednesday, December 05, 2007
Well this blog, the wiki and all the other associated services disappeared for most of today... thanks to the magic of NoAuth ... for no reason that anyone could explain to me beyond "it sometimes happens" - Orcon lost Authority over my business DSL connection, and it reverted back to Telecom/Xtra (New Zealands "legacy" telco)... and it took must of today for them to get the authority changed back, I don't think this was any fault of Orcons, but it still pissed me off no end.

So appologies to anyone wondering where my blog went, or any of the related services - I do have plans to migrate the blog to US servers in the not to distant future.  And big appologies to all the Architecture chat participants, sorry I couldn't get a reminder email out sooner.

On a different note - It did identify a need for me to maintain a secondary connection, with a static IP, that I can fall back to if required... thankfully the three concurrent projects that have been in UAT over the last couple of weeks have all wound down now - so it didn't have as much impact as it could of.

Though at least they wouldn't have been able to log bugs about servers being unavailable, Trac would've been unavailable too ;o)

... so any suggestions as to what would make an adequate fall back technology (something other then DSL, I've used wired country two-way-radio to the sky tower in the past which has worked well, but it doesn't give much "bang" for buck and has a high up-front cost last time I checked) ... I'm not too worried about 1 day downtime, but if this had been 3 or 4 days it would've been quite damaging for me, customers and those intangibles like reputation.


At any rate - thanks for the patience, and I'll see some of you at the Architecture Chat tomorrow.

Cheers,

 - Alex
posted @ Wednesday, December 05, 2007 10:34:04 AM (New Zealand Daylight Time, UTC+13:00)    Comments [1] | Trackback |
 Monday, October 16, 2006

Trac

I know it's not .Net, but that's not always a bad thing...

Well I tend to keep my eye out for things which take my fancy, or more importantly, free things that take my fancy...

Trac is one of those things, a project management and bug database web app written in Python, written by edgewall software... it's actually pretty cool... for RoR enthusiasts they're probably aware of it's existence already (considering RoR use this for manging their bugs and patches)

First off, you can run it on windows...  though a *nix is generally better, and it's not too hard to get going - took me about half an hour... second thing, it's a lot better once you install a few hacks especially:
Now, at this point what makes it good?
  • SVN Integration
  • Wiki Integration
  • Extensibility and community support
Basically I can check in code with comments containing wiki markup and references to existing "tickets" logged in the system... The wiki integration incidentally is my primary motivation for looking so closely at using this for some of the projects I've been working on... I'm a liability to Seismic Technologies... documenting build processes etc. in a wiki are one way we can minimize that... We already have a customer-focused wiki, but it doesn't encourage maintenance on a day-to-day basis like this product does.

secondary to that is the very good subversion integration, a must considering that's the only source control system I use.

Creating tickets via XML-RPC in C#

Now the last hack I suggested exposes plenty of functionality in the system via XML RPC... at which point we can start logging bugs remotely, from C#... to do this we'll need a copy of the xmlrpc.net library

I'm not conducting a tutorial here ;o) but if Trac takes your fancy you might find this code handy... first off this is our interface for logging tickets in Trac - incidentally this is only a small subset of the funcionality exposed.

[XmlRpcUrl("http://localhost/trac/login/xmlrpc")]

public interface ITicket : IXmlRpcProxy

{       

    [XmlRpcMethod("ticket.query")]

    int[] Query(string qstr);

 

    [XmlRpcMethod("ticket.getRecentChanges")]

    int[] GetRecentChanges(DateTime since);

 

    [XmlRpcMethod("ticket.getAvailableActions")]

    string[] GetAvailableActions(int id);

 

    [XmlRpcMethod("ticket.getTicketFields")]

    TicketField[] GetTicketFields();

 

    [XmlRpcMethod("ticket.create")]

    int Create(string summary, string description, XmlRpcStruct attributes);

 

    [XmlRpcMethod("ticket.get")]

    object[] GetTicket(int id);

 

    [XmlRpcMethod("ticket.delete")]

    void Delete(int id);

 

    [XmlRpcMethod("ticket.update")]

    object[] Update(int id, string comment, XmlRpcStruct attributes);

 

    [XmlRpcMethod("ticket.type.getAll")]

    string[] GetAllTypes();

 

    [XmlRpcMethod("ticket.resolution.getAll")]

    string[] GetAllResolutions();

 

    [XmlRpcMethod("ticket.priority.getAll")]

    string[] GetAllPriorities();

 

    [XmlRpcMethod("ticket.component.getAll")]

    string[] GetAllComponents();

 

    [XmlRpcMethod("ticket.version.getAll")]

    string[] GetAllVersions();

 

    [XmlRpcMethod("ticket.severity.getAll")]

    string[] GetAllSeverities();

 

    [XmlRpcMethod("ticket.milestone.getAll")]

    string[] GetAllMilestones();

}


Now, tickets have a bunch of attributes associated with them...

public static class TicketAttributes

{

    public const string Cc = "cc";

    public const string Keywords = "keywords";

    public const string Status = "status";

    public const string Type = "type";

    public const string Owner = "owner";

    public const string Version = "version";

    public const string Resolution = "resolution";

    public const string Reporter = "reporter";

    public const string Milestone = "milestone";

    public const string Component = "component";

    public const string Summary = "summary";

    public const string Description = "description";

    public const string Priority = "priority";

}


And we can make ticket information easier to get a hold of with a simple class:

/// <summary>

/// represents the information for a ticket

/// </summary>

public class TicketInfo

{

    private int _ticketId;

    private DateTime _created;

    private DateTime _lastModified;

    private XmlRpcStruct _attributes;

 

    public TicketInfo()

    {

    }

 

    public TicketInfo(object[] values)

    {

        Update(values);

    }

 

    internal void Update(object[] values)

    {

        if (values == null) throw new ArgumentNullException("values");

        if (values.Length != 4) throw new ArgumentException("values should have 4 elements");

 

        _ticketId = (int)values[0];

        _created = DateHelper.ParseUnixTimestamp((int)values[1]);

        _lastModified = DateHelper.ParseUnixTimestamp((int)values[2]);

        _attributes = (XmlRpcStruct)values[3];

    }

 

    /// <summary>

    /// The identifier for this ticket

    /// </summary>

    public int TicketId

    {

        get { return _ticketId; }

        set { _ticketId = value; }

    }

 

    /// <summary>

    /// date and time the ticket was created

    /// </summary>

    public DateTime Created

    {

        get { return _created; }

        set { _created = value; }

    }

 

    /// <summary>

    /// date and time the ticket was last modified

    /// </summary>

    public DateTime LastModified

    {

        get { return _lastModified; }

        set { _lastModified = value; }

    }

 

    /// <summary>

    /// The attributes for this ticket, this will include any additional fields

    /// that aren't defined explicitly as members of this class.

    /// </summary>

    public XmlRpcStruct Attributes

    {

        get

        {

            if (_attributes == null) _attributes = new XmlRpcStruct();

            return _attributes;

        }

        set { _attributes = value; }

    }

 

    public string Cc

    {

        get { return GetAttribute(TicketAttributes.Cc); }

        set { SetAttribute(TicketAttributes.Cc, value); }

    }

 

    public string Keywords

    {

        get { return GetAttribute(TicketAttributes.Keywords); }

        set { SetAttribute(TicketAttributes.Keywords, value); }

    }

 

    public string Status

    {

        get { return GetAttribute(TicketAttributes.Status); }

        set { SetAttribute(TicketAttributes.Status, value); }

    }

 

    public string Type

    {

        get { return GetAttribute(TicketAttributes.Type); }

        set { SetAttribute(TicketAttributes.Type, value); }

    }

 

    public string Owner

    {

        get { return GetAttribute(TicketAttributes.Owner); }

        set { SetAttribute(TicketAttributes.Owner, value); }

    }

 

    public string Version

    {

        get { return GetAttribute(TicketAttributes.Version); }

        set { SetAttribute(TicketAttributes.Version, value); }

    }

 

    public string Resolution       

    {

        get { return GetAttribute(TicketAttributes.Resolution); }

        set { SetAttribute(TicketAttributes.Resolution, value); }

    }

 

    public string Reporter       

    {

        get { return GetAttribute(TicketAttributes.Reporter); }

        set { SetAttribute(TicketAttributes.Reporter, value); }

    }

 

    public string Milestone

    {

        get { return GetAttribute(TicketAttributes.Milestone); }

        set { SetAttribute(TicketAttributes.Milestone, value); }

    }

 

    public string Component

    {

        get { return GetAttribute(TicketAttributes.Component); }

        set { SetAttribute(TicketAttributes.Component, value); }

    }

 

    public string Summary

    {

        get { return GetAttribute(TicketAttributes.Summary); }

        set { SetAttribute(TicketAttributes.Summary, value); }

    }

 

    public string Description

    {

        get { return GetAttribute(TicketAttributes.Description); }

        set { SetAttribute(TicketAttributes.Description, value); }

    }

 

    public string Priority

    {

        get { return GetAttribute(TicketAttributes.Priority); }

        set { SetAttribute(TicketAttributes.Priority, value); }

    }

 

    #region Support methods

 

    private string GetAttribute(string name)

    {

        if (Attributes.Contains(name))

        {

            return Convert.ToString(Attributes[name]);

        }

        return null;

    }

 

    private void SetAttribute(string name, string value)

    {

        if (Attributes.Contains(name))

        {

            Attributes[name] = value;

        }

        else

        {

            Attributes.Add(name, value);

        }

    }

 

    #endregion

}


And last of all we have a class for managing tickets.. by the way this isn't complete, I haven't finished writing it because this is just one of my back burner projects...

public class TicketManager

{

    private ITicket _ticket;

 

    public void Connect(string url, string userName, string password)

    {

        _ticket = XmlRpcProxyGen.Create<ITicket>();

        _ticket.Url = url;

        _ticket.PreAuthenticate = true;

        _ticket.Credentials = new NetworkCredential(userName, password);

    }

 

    public string[] GetAvailableActions(int id)

    {

        return _ticket.GetAvailableActions(id);

    }

 

    public string[] GetAvailableActions(TicketInfo ticket)

    {

        ValidateTicket(ticket);

        return _ticket.GetAvailableActions(ticket.TicketId);

    }

 

    public int[] GetRecentChanges(DateTime since)

    {

        return _ticket.GetRecentChanges(since);

    }

 

    public void DeleteTicket(int ticketId)

    {

        _ticket.Delete(ticketId);

    }

 

    public void DeleteTicket(TicketInfo ticket)

    {

        ValidateTicket(ticket);

        DeleteTicket(ticket.TicketId);

    }

 

    public void UpdateTicket(TicketInfo ticket, string comment)

    {

        ValidateTicket(ticket);

        object[] values = _ticket.Update(ticket.TicketId, comment, ticket.Attributes);

        ticket.Update(values);

    }

 

    public void CreateTicket(TicketInfo ticket)

    {

        if (string.IsNullOrEmpty(ticket.Summary)) throw new ArgumentNullException("ticket.Summary");

        if (string.IsNullOrEmpty(ticket.Description)) throw new ArgumentNullException("ticket.Description");

        if (string.IsNullOrEmpty(ticket.Type)) throw new ArgumentNullException("ticket.Type");

        if (string.IsNullOrEmpty(ticket.Priority)) throw new ArgumentNullException("ticket.Priority");

        if (string.IsNullOrEmpty(ticket.Component)) throw new ArgumentNullException("ticket.Component");

 

        XmlRpcStruct tempAttributes = new XmlRpcStruct();

        foreach (object key in ticket.Attributes.Keys)

        {

            if ((((string)key) != TicketAttributes.Description) && (((string)key) != TicketAttributes.Summary))

            {

                tempAttributes.Add(key, ticket.Attributes[key]);

            }

        }

 

        int id = _ticket.Create(ticket.Summary, ticket.Description, ticket.Attributes);

        ticket.TicketId = id;

    }

 

    private void ValidateTicket(TicketInfo ticket)

    {

        if (ticket == null) throw new ArgumentNullException("ticket");

        if (ticket.TicketId <= 0) throw new ArgumentException("ticketId must be greater then 0");

    }

}


About the only trick here is that some of the date values get returned as unix time stamps so you need to convert them...

public static DateTime ParseUnixTimestamp(double timestamp)

{

    return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(timestamp);

}


Because the Syzmk Rich Media Processor product I work on is a clearing house for simple messages among other things... It becomes a small jump from here (about 30 lines of C# code, 5 for a quick and dirty "IronPython" script) to start submitting tasks and bugs via email or mobile... who knows, it could be handy if you get a call while your out of the office and need to log a bug or task.
 |  | 
posted @ Monday, October 16, 2006 8:37:48 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |
Search
FeedCount

Tags...
Who am I?
Alex Henderson
Alex Henderson
Auckland, New Zealand
Managing Director at Dev|Defined Limited

"Self Confessed Coding Junky for 15 years"
View Alex Henderson's profile on LinkedIn
 
Mobile: +64-21-402-969
Email: bittercoder 'at' gmail 'dot' com
MSN: bittercoder_nz@hotmail
Skype: alex.devdefined
Navigation