![]() |
Desktop Development »
Miscellaneous »
Windows Forms
Intermediate
License: The Code Project Open License (CPOL)
Developing Jira client with Jira SOAP API for PragmaSQL T-SQL EditorBy Ali Ozgur, Tolga KurkcuogluDescribes how to develop a custom Jira client by using PragmaSQL add-in support |
C# (C# 2.0), Windows (Win2K, WinXP, Win2003, Vista), .NET (.NET 2.0), Win32, Visual Studio (VS2005), Architect, Dev, Design
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
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 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.
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.
HostServicesSingleton.HostServices.ConfigSvc provided by the PragmaSQL.Core
to serialize user credentials and preferences.
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.
To learn how to install PragmaSQL addins read this article.
This article originally accompanies the first release of PragmaSQL JIRAClient Add-In
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 13 Feb 2008 Editor: |
Copyright 2008 by Ali Ozgur, Tolga Kurkcuoglu Everything else Copyright © CodeProject, 1999-2009 Web16 | Advertise on the Code Project |