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 (_getFromFilterAsyncState != null)
_service.CancelAsync(_getFromFilterAsyncState);
_getFromFilterAsyncState = new UserAsyncState(Guid.NewGuid());
_service.getIssuesFromFilterAsync(_auth, f.id, _getFromFilterAsyncState);
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 (_filterAsyncResult == null)
return;
_getFromFilterAsyncState = _getFromFilterAsyncState == e.UserState ? null : _getFromFilterAsyncState;
if (e.Cancelled)
{
foreach (IssueListItem item in _filterAsyncResult)
_service.CancelAsync(item);
_filterAsyncResult.Cancel();
}
else
{
_filterAsyncResult.Clear();
foreach (RemoteIssue issue in e.Result)
{
IssueListItem item = new IssueListItem();
item.IssueKey = issue.key;
_filterAsyncResult.Add(item);
}
_filterAsyncResult.FetchDetailsAsync();
}
if (_onGetFromFilterCompleteNotify != null)
_onGetFromFilterCompleteNotify();
}
The Cancel button calls the following method to cancel pending requests in frmMyFilter
public void RunSavedFilterAsyncCancel(object userState)
{
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