Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing Jira client with Jira SOAP API for PragmaSQL T-SQL Editor

0.00/5 (No votes)
13 Feb 2008 1  
Describes how to develop a custom Jira client by using PragmaSQL add-in support
  • Download binaries - 200 KB
  • Download JiraAddIn_Source.zip - 1.08 MB

    Introduction

    T-SQL procedures tend to change quickly over time as revealed bugs are fixed, bussiness rules change and the need for refactoring arises. One has to keep track of what changes were made for what reason and preferrably attach some bits of additional information, futhermore he should be able to conduct a search on the changes and the attached information. Comments in the source code may help a little, but after a while they outgrow the source code itself, outer links are uneasy to browse and unsearchable without some hacking.

    The solution is employing a bug tracking system, documenting the task, dependencies and the history of the procedure in it and commenting the source code with the key it generates.

    Background

    PragmaSQL is a neat T-SQL editor developed in C# utilizing the IC#Code Add-In architecture. It exposes some of the underlying features wrapped and classified within some neat classes.

    JIRA is my favourite bug tracking system. It provides a web service with which its functionality can be automated.

    When I figured out that the editor can be extended, I decided that a JIRA client add-in to the editor might ease to manage the development cycle for the t-sql procedures which I have plenty of in my system. After I digged into the PragmaSQL.Core assembly and got some help from the author Ali Ozgur, this insight formed into a project.

    Using the Code

    In order to integrate the custom add-in code into the PragmaSQL application, an xml description is used. The format should conform the rules defined in SODA by Mike Krueger. Information on how to integrate into the main menu, specific context menus, conditions on which to enable and disable the integrated items is specified in a well defined structure.

    PragmaSQL.Core exposes a service through which interaction with the content of the editor is possible. For example, HostServicesSingleton.HostServices.EditorServices enables the add-in code to query the states of the editor windows in use.

    The .addin description file

    SODA specification reveals the inner workings of the convention in detail, however it might be of practical interest to examine a few segments of the description file. Nonetheless, another add-in for PragmaSQL is implemented by the author Ali Ozgur. His article on External Tools Add-in might also shed light upon some implementation details.

    The following segment of the JIRAClient.addin description file subscribes to some predefined events related to the configuration service in the PragmaSQL.Core.dll. The service is reachable through HostServicesSingleton.HostServices.ConfigSvc. The command specified under the path with name Workspace/Autostart is run when the application starts and is a good place for hooking to events triggered by the PragmaSQL.Core.

      . . .
      
      <Path name = "/Workspace/Autostart">
        <Class id = "JIRAClientSubscribeToEventsCommand" 
               class = "JIRAClientAddin.SubscribeToEventsCommand"/>
      </Path>
      
      . . .
    
    
    The implementation of JIRAClientAddin.SubscribeToEventsCommand below tells that the ucJIRAOptions has some handlers for the configuration service. AbstractMenuCommand is defined in ICSharpCode.Core.dll and custom commands are required to derive from it.

    A little notice should be made that ucJIRAOptions is actually needed to be a user control implementing the IConfigContentEditor interface. The events are thrown accordingly when the user selects Tools/Options from the main menu and the user control is rendered within the options form. If it were a form object, it would not be possible to render it inside a wrapper form. This provides a standard way to serialize user preferences both for the core application and the installed add-in code.

      public class SubscribeToEventsCommand : AbstractMenuCommand
      {
        public override void Run()
        {
          HostServicesSingleton.HostServices.ConfigSvc.DialogOpened   += ucJIRAOptions.ConfigSvc_DialogOpened;
          HostServicesSingleton.HostServices.ConfigSvc.DialogClosed   += ucJIRAOptions.ConfigSvc_DialogClosed;
          HostServicesSingleton.HostServices.ConfigSvc.FinalSelection += ucJIRAOptions.ConfigSvc_FinalSelection;
        }
      }
    
    Functionality of the add-in is made reachable in the main menu via the following segment in the JIRAClient.addin description file. Declaration of the first menu item describes that the class BrowseIssueCommand is responsible for providing an entry point for the feature named "Browse issue". The class should implement a command pattern in the JIRAClientAddin.dll. In this way, PragmaSQL.Core.dll expects a Run method to exist in the class and guarantees that it will call this method when the item is selected in the main menu.
      <Path name = "/Workspace/ToolsMenu">
        <MenuItem id ="JIRAClientMainMenu"
                  label="JIRA" 
                  type="Menu">
    
        <MenuItem id = "JIRAClientMainMenu_BrowseIssue"
                  label = "Browse issue"
                  class = "JIRAClientAddin.BrowseIssueCommand"
                  shortcut="F4"/>
      . . .
            
        </MenuItem>
      </Path>
    
    
    Below is the BrowseIssueCommand class.
      public class BrowseIssueCommand : AbstractMenuCommand
      {
        public override void Run()
        {
          IssueBrowser.BrowseIssue();
        }
      }
    
    The following part registers the same feature into the context menu of the editor window.
      
      . . .
      
      <Path name = "/Workspace/ScriptEditor/ContextMenu">
        <MenuItem id ="JIRAClientContextMenu_TopSeparator"
                  type="Separator"/>
    
        <MenuItem id ="JIRAClientContextMenu_JIRAContext"
                  label="JIRA"     
                  type="Menu">
            
        <MenuItem id = "JIRAClientContextMenu_BrowseIssue"
                  label = "Browse issue"
                  class = "JIRAClientAddin.BrowseIssueCommand"
                  shortcut="F4"/>
      . . .   
    
        </MenuItem>
      </Path>      
      
      . . .   
      
    
    There are a few more paths defined in the PragmaSQL.Core with which to integrate custom code. A little session of trial and error makes it clear. There is a Base.addin file under a folder named AddIns in the folder where PragmaSQL application is installed. The Base.addin file lists available path names that the application exposes. The software, actually, makes use of the architecture to interface with its own features, too.

    The JIRA SOAP API

    JIRA implements a full blown web service and its object model can easily be proxied by just adding a web reference to the C# project. Web reference should target the /rpc/soap/jirasoapservice-v2?wsdl path of your JIRA installation. A documentation on classes and relations among them is provided and it is pretty much complete.

    Within the client add-in, a class named JIRAFacade wraps the calls to the JIRA service for convenience, provides a simple cache mechanism and some error checking. JIRAClientAddin namespace houses the user interface and depends on the JIRAFacade. In the user interface, asynchronous methods are preferred for might-be time consuming operations in order not to hang the application itself.

    Features provided by the JIRAClient add-in

    • Create JIRA issue : Activating this functionality brings out a form for issue entry. After the issue is created, a reference line is dropped onto the cursor position. Feature is bound to the ALT-J key combination.
    • Browse Issue : This functionality scans through the current editor for issue references and collects them into a list from which the user may choose one. A double click on the list opens the built-in web browser and navigates to the selected issue. For convenience, the list is supressed if there is just one reference or a reference is already highlighted and the browser navigates directly. Feature is bound to the F4 key.
    • Cycle through : This is a supporting feature for issue browsing. Activating it scans and highlights references one by one in the current editor making it easy to see the references int source code. Hitting F4 when a reference is highlighted, makes the browser to navigate to it directly. Feature is bound to CTRL-J key combination.
    • My Filters : This might be considered the main feature. JIRA does not currently allow posting dynamically built custom criteria as of version 3.12.1, however one can remotely invoke a formerly prepared custom set of criteria (or saved searches, as one might call it). The prepared set of custom criteria is called a filter and there are methods by which one can retrieve the filters, run them and retrieve the issues that they match. What the feature of the add-in does is merely to provide a GUI for the purpose and give a few extra on the context menus.
    • Options : The add-in makes use of the basic HostServicesSingleton.HostServices.ConfigSvc provided by the PragmaSQL.Core to serialize user credentials and preferences.

    Points of Interest

    The main aspect of the project was to build an add-in for PragmaSQL. JIRA SOAP API was also fun to dig in. It provided a test bed for me to improve my C# skills but there is, actually, nothing new in it for a C# veteran. The included code speaks for itself, some parts might be considered clever where as some parts are naive. Being not so much commented, some parts might be hard to grasp at the first glance. I made an effort to fix the bugs I came across but there may still be a number of them scattered around. Nevertheless, an interesting task worth mentioning might be fetching the issues when a filter is double clicked.

    Asynchronous filter fetch in MyFilters

    Below are some of the fields related to this task in the JIRAFacade singleton. Structure of the UserAsyncState does not actually have any significance, apart from the fact that the facade has or has not an instance of it at a given time. The web service method _service.getIssuesFromFilterAsync delivers only the issue keys for the matched issues. A second pass is needed to fetch the relevant detail for each issue returned. This is to say that, we should first get the filter result and further request the detail for each issue. Once again the request for details will be asynchronous, too, via the web service method _service.getIssueAsync. The IssueList class is designed to account for the timing and event handling and is, merely, a container for IssuelistItem data objects.
      private JiraSoapServiceService _service = new JiraSoapServiceService();
      private IssueList _filterAsyncResult;
      private UserAsyncState _getFromFilterAsyncState = null;  
      
      public delegate void OnGetFromFilterCompleteEvent();
    
      private OnGetFromFilterCompleteEvent _onGetFromFilterCompleteNotify;
      public OnGetFromFilterCompleteEvent OnGetFromFilterCompleteNotify
      {
        get { return _onGetFromFilterCompleteNotify; }
        set { _onGetFromFilterCompleteNotify = value; }
      }  
    
    In frmMyFilters, when an item is double clicked in the listbox, filter name is parsed and it is fed into the method below along side a reference to a IssueList instance which will eventually hold the issues that will be fetched.
      public UserAsyncState RunSavedFilterAsync(string filterName, IssueList filterResult)
      {
        _filterAsyncResult = filterResult;
        RemoteFilter f = this.FilterByName(filterName);
    
    // if it is not null, there is already a filter on the run.
    // apperantly, the user has double clicked another filter before the previous double clicked filter 
    // finishes its run. cancel it and run the newly clicked filter.
        if (_getFromFilterAsyncState != null)  
          _service.CancelAsync(_getFromFilterAsyncState);
    
        _getFromFilterAsyncState = new UserAsyncState(Guid.NewGuid());
        
    // request from web service            
        _service.getIssuesFromFilterAsync(_auth, f.id, _getFromFilterAsyncState);
        
    // let caller to know that a filter is running.    
        return _getFromFilterAsyncState;
      }
    
    The private method OnGetFromFilterAsyncComplete is wired to _service.getIssuesFromFilterCompleted event in the constructor.
       . . . 
      _service.getIssuesFromFilterCompleted += new getIssuesFromFilterCompletedEventHandler(OnGetFromFilterAsyncComplete);
       . . .
    
    It will be run when the request completes and give us an opportunity for further process.
      private void OnGetFromFilterAsyncComplete(object sender, getIssuesFromFilterCompletedEventArgs e)
      {
    // if we still have a container to hold the IssueItems carry on else silently exit. 
    // An appropriate exception might be thrown, also
        if (_filterAsyncResult == null)
          return;
           
    // release the state object    
        _getFromFilterAsyncState = _getFromFilterAsyncState == e.UserState ? null : _getFromFilterAsyncState;    
        
    // if the request is cancelled before it completes, cancel further processing        
        if (e.Cancelled)
        {
    // cancel if detail for any IssueListItem has been requested and is on its way    
          foreach (IssueListItem item in _filterAsyncResult)
            _service.CancelAsync(item); 
            
    // let the container IssueList know that request is cancelled            
          _filterAsyncResult.Cancel(); 
        }
        else        
        {
    // empty the container    
          _filterAsyncResult.Clear();
          
    // insert newly fetched issues and request their details         
          foreach (RemoteIssue issue in e.Result)
          {
            IssueListItem item = new IssueListItem();
            item.IssueKey = issue.key;
            _filterAsyncResult.Add(item);
          }
          _filterAsyncResult.FetchDetailsAsync();
        }
    
    // notify listeners that we are done with the filter and probably waiting for the details to complete                       
        if (_onGetFromFilterCompleteNotify != null)
          _onGetFromFilterCompleteNotify();          
        
      }
    
    The Cancel button calls the following method to cancel pending requests in frmMyFilter
      public void RunSavedFilterAsyncCancel(object userState)
      {
    // cancel the getFromFromFilter request
    // if detail for any IssueListItem has been requested and is on its way, cancel them too 
        if (_filterAsyncResult != null)
          foreach (IssueListItem item in _filterAsyncResult)
            _service.CancelAsync(item);  
              
        _service.CancelAsync(userState);        
      } 
    

    In the IssueList class, similar outfit is tailored for asynchronous issue fetch alongside some infrastructure for some goodies like a progress bar. FetchDetailsAsync method makes the requests for details through the facade, IssueListItemCompleteEvenHandler collects the information that an issue detail has completed and fires an OnFetchDetailsCompleteEvent when all the details are completed. Notice that, in this way the grid in frmMyFilters refreshes just once.

    AddIn Installation

    To learn how to install PragmaSQL addins read this article.

    History

    This article originally accompanies the first release of PragmaSQL JIRAClient Add-In

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here