J tricks - Little JIRA Tricks
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us

Extending JIRA Actions

10/6/2010

103 Comments

 
There are so many user stories for this one! How do you override some of the JIRA built in actions? How do you do some additional stuff in the JIRA built in action? Like doing some crazy things immediately after creation before the page return to the user? Or doing some innovative validations on some of those actions? Or do what is asked in this thread ?

Here is the simple answer to all those questions. Extends the JIRA actions using the webwork plugin module. There could be some alternatives in few cases (like handling it in a listener) but not always.

Webwork plugin module is designed to add more actions on to JIRA as well as to extend or override the existing actions. So how does all this work?

Let us quickly have a look at the actions.xml under WEB-INF/classes in your JIRA installation directory. This is where all of the JIRA's actions are defined. Here is the snippet that defines the CreateIssue action.

<action name="issue.CreateIssueDetails" alias="CreateIssueDetails">
        <view name="error">/secure/views/createissue-details.jsp</view>
        <view name="input">/secure/views/createissue-details.jsp</view>
</action>

Note: JIRA has both CreateIssue and CreateIssueDetails action. It is the latter which actually creates the Issue. The former is to select the the project and issutype and to proceeed to the issue creation form.

So we have the CreateIssueDetails action which uses the CreateIssueDetails class and the associated jsps. What we will do here is just override this action with our custom action class. We will provide an additional validation and also print some statements after the issue creation. Printing statements is ofcourse not a helpful exmaple but that is just a placeholder where you can do whatever you want to!

Here is how the atlassian-plugin.xml looks like when we override the JIRA action.

<webwork1 key="jtricks-create-issue-details" name="JTricks Create Issue Details" >
    <actions>
        <action name="com.jtricks.MyCreateIssueDetails" alias="CreateIssueDetails">
        <view name="error">/secure/views/createissue-details.jsp</view>
        <view name="input">/secure/views/createissue-details.jsp</view>
    </action>
    </actions>
</webwork1>

As usual you need a unique key here. Note that we haven't changed the jsps. But if you want to, you can do that as well! It comes with the extra cost of re-building the war file with the jsp in the edit-webapp (for production instances) and maintaing the version for future upgrades.

Coming back to our example, I have given a custom action class here com.jtricks.MyCreateIssueDetails. As I am not giving a full action class implementation here, I would start with extending the action.

public class MyCreateIssueDetails extends CreateIssueDetails{

It will ask you to add the constructor and so on. Now we override the methods you want. Let us see an example for validation:

@Override
protected void doValidation() {
    //My custom validation here
    String assignee = getSingleValueFromParameters("assignee");
    if (assignee == null || !assignee.equals(getRemoteUser().getName())){
        addErrorMessage("Assignee is not the current user, got U!");
    }
    super.doValidation();
}

Here we add an error to the error collection if the assignee is not the current user. Just for the sake of an example! Now let us look at how to do the post creation steps.

@Override
protected String doExecute() throws Exception {
    String ret_val  = super.doExecute();
    if (!ret_val.equals(Action.ERROR)){
        postCreationTasks();
    }
    return ret_val;
}

All I am doing is to call the actual Create issue Details's doExecute method and do additional stuff if the retunr value is not error. In the postCreationTasks method, do you stuff like printing something as I do in the example code attached below.

Hopefully that gives an overview of overriding actions in JIRA. The full source for the example can be downloaded below. Feel free to add any suggestions or feedbacks. Have a blast!
extend-action-plugin.zip
File Size: 3 kb
File Type: zip
Download File

103 Comments
Matt Doar link
10/6/2010 06:09:17 am

Have you noticed that when you change the class for an Action, the i18n in the vm or jsp file doesn't pick up the i18n resource from your plugin? I sometimes end up using the com.atlassian package just so the i18n continues to work.

~Matt

Reply
J-Tricks
10/6/2010 06:15:16 am

Yes, I have. In fact I faced the same issue in my 'Copy To Subtask' plugin! But this is only post 4.x. I have seen in the forums a workaround for this by overriding the getText() method. Yet to test it :)

Reply
csytsma
10/19/2010 04:25:23 am

If you only wanted to add functionality on the Create Action for specific projects, how would you go about it? Would you use this same method, or use a Listener instead?

Thanks!

Reply
J-Tricks
10/19/2010 04:45:20 am

It will depend on the functionality! In most cases , I will choose a listener because you can have as many listeners as you want on the same event. But you can't have 2 plugins extending the same action!

Having said that, there is a problem with listeners. They are late! For eg: if you want to do some thing on Create and give that info back to the user when the issue page is presented to the user, listeners might not help. You will need to refresh the page as the listeners might not have done the job by the time you are on View issue page. In those cases, extending actions is perfect.

A more useful scenario will be to handle some validations etc which you can't do in listeners.

Reply
Christina
2/24/2011 09:33:49 am

How did you get the parameter fields though (the "assignee")? I'm trying to extend the native Jira link issue action to validate if certain types of issues can have a certain link. But in my code, I seem to be able to get the other parameter names right (comment and linkDesc(link type) and linkKey(child issue)) but I just can't get the current issue that called the link action!

Any hints or ideas?

And thanks for the post! It was very very helpful.

Reply
J-Tricks
2/24/2011 09:48:20 am

Just call getIssue() or getIssueObject() within your validate method. That should give you the original issue GenericValue or MutableIssue object respectively.

Reply
Christina
2/28/2011 12:56:10 pm

So now I'm stuck on another part of the plugin. I have a properties file that I put into the src/main/resources folder, and so it's in the jar file that I put into the Jira installed-plugins folder. However, I'm not sure what its absolute path is that I should put into the code.

Any pointers here?

Thanks so much again :)

Reply
J-Tricks
2/28/2011 06:18:03 pm

Christina,

The path should be relative to the src/main/resources folder. If the file is under this folder with the name test.properties, you can refer it directly. If you notice in the jar, you can find the file at the root level.

Hope it helps!

Reply
Christina
3/4/2011 03:32:26 pm

That is exactly what I have currently. I put it into src/main/resources and it is in the root level of the jar. I've tried:

getClass().getResourceAsStream("test.properties");
this.getClass().getClassLoader().getResourceAsStream("test.properties");
MyClass.class.getResourceAsStream("test.properties");

with variations of test.properties and /test.properties among many others. But nothing seems to be working. Right now I've only gotten this properties file to work if I specify an absolute path outside of the jar.

Reply
J-Tricks
3/4/2011 07:22:05 pm

Did you add it as a downloadbale resource in the atlassian-plugin.xml?

http://confluence.atlassian.com/display/JIRA/Downloadable+Plugin+Resources

Something like: <resource type="i18n" name="i18n" location="test" />

Reply
J-Tricks
3/4/2011 07:24:25 pm

Another useful link for internationalization : http://www.opensymphony.com/webwork/wikidocs/Internationalization.html

Reply
Christina
3/7/2011 10:53:42 am

Thanks it works now! The only thing is that the catalina.out file gives me this error message:

Failed to get resource bundle resources/test from resource descriptor test

where my resource element looks like this:

<resource type="i18n" name="test" location="resources/test" />

But thanks again =)

Reply
orchest
6/19/2011 11:30:15 pm

hi
i have an error in (import com.atlassian.jira.bc.issue.IssueService;), what i have to do to fix it .
i'm using jira 3.13.1
thank you

Reply
J-Tricks
6/20/2011 09:14:57 pm

@orchest IssueService is not available in 3.13.1. What are you trying to do using IssueService? You will have to use IssueManager (http://docs.atlassian.com/jira/3.13.1/com/atlassian/jira/issue/IssueManager.html) in 3.13.1 for most operations.

Reply
orchest
6/21/2011 01:03:17 am

i want to create a plugin that can make a search in emails and retrieve an email issue and when i get this issue i would like to create a subtask for it

Reply
J-Tricks
6/22/2011 03:19:54 am

orchest, you will have to write a custom mail handler for it and then process the mail you receive!

You can read about JIRA's mail handler at http://confluence.atlassian.com/display/JIRA/Creating+Issues+and+Comments+from+Email

Reply
orchest
6/22/2011 04:16:53 am

thank you for answering , now i would like to make to get all my project and i would like to select one of them and then i would like to see all issues of this project and to move this issue to a subtask .
thank you in advance :)

Reply
jiraMan
6/23/2011 04:26:54 am

hi ,
i created a plugin in jira that retrieves project and he puts them in a combobox , i would like to find issues of this project and put them in other combobox programmatically (i don't find the class of jira that retrieves the issues of the project selected in jira 3.13)
please help pleaseeeeeeee

Reply
J-Tricks
6/23/2011 07:18:22 am

@jiraMan Check it out in the following link:
http://confluence.atlassian.com/display/JIRA03x/How+to+search+for+Issues+from+within+a+Plugin

Nice name btw!

Reply
jiraMan
6/27/2011 04:29:50 am

"jiraMan" is only a desire and not an affermation coz i need to study so much to be a jira man , i'm here again to say that i did not know how to replace "targetIssueType" in the plugin "create and link" "https://plugins.atlassian.com/plugin/details/4964" with issueSummary of the selected project ...
sorry for disturbing again.

Reply
J-Tricks
6/27/2011 08:19:57 am

@jiraMan Frankly, I haven't used the plugin before. I can answer generic questions but in this case I would approach Matt who is the plugin author!

Reply
Joe Caputo
7/14/2011 06:40:05 pm

Hi there,

So I guess this is how I would also change the behavior of the GroupBrowserPicker? I don't want to change the GroupBrowserPicker functionality altogether, but only for my custom field. What would be the best way to do this? For my multi group custom field, I want the current user to see only the groups that they are a member of.

Btw, this is a great site. Thank you for posing so much useful information. It is greatly appreciated!

Reply
J-Tricks
7/14/2011 07:17:58 pm

@Joe Thanks :)

You have got 2 options. Either create a new action that extends the GroupBrowserPicker similar to this (with a new alias) and invoke that in your custom field.

Or extend the action like this and add the logic to return groups based on whether it is invoked from the customfield or somewhere else.

The first option seems best to me!

Reply
Joe Caputo
7/15/2011 09:40:52 am

Hi there.

Thank you so much for your response.

It seems my action isn't used by by custom field plugin. here is my atlassian-plugin.xml file so far:

<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
</plugin-info>

<!-- Group User Picker CF: START -->
<customfield-type key="securegroupcf" name="Secure Group List" class="com.ea.jira.plugins.securegroup.SecureGroupCFType">
<description key="com.ea.jira.plugins.securegroup.securegroupcftype.desc"/>
<param name="multiple" value="true" />
<resource type="velocity" name="view" location="templates/plugins/fields/view/view-multigroup.vm"/>
<resource type="velocity" name="column-view" location="templates/plugins/fields/view/view-multigroup.vm"/>
<resource type="velocity" name="edit" location="templates/plugins/fields/edit/edit-multigroup.vm"/>
<resource type="velocity" name="xml" location="templates/plugins/fields/xml/xml-multiselect.vm"/>
<resource type="i18n" name="i18n" location="com.ea.jira.plugins.securegroup.utils.i18n"/>
</customfield-type>


<webwork1 key="securegroupbrowser" name="Secure Group Browser Picker" >
<actions>
<action name="com.ea.jira.plugins.securegroup.SecureGroupBrowser" alias="SecureGroupBrowser">
<view name="success">/secure/admin/user/views/groupbrowser.jsp</view>
<view name="error">/secure/admin/user/views/groupbrowser.jsp</view>
</action>
</actions>
</webwork1>









</atlassian-plugin>

I have a class called SecureGroupBrowser in the appropiate package but it seems to never work? Can you see anything I'm doing wrong? Thank you so much for your time.

-Joe

Reply
J-Tricks
7/18/2011 01:09:10 am

Joe,

This looks al-right to me. How are you invoking the action?

Reply
Joe Caputo
7/18/2011 05:25:23 am

Hi there! Thank you very much for getting back to me so quickly. Sorry I'm being such a pain!

I think that's what I'm not doing. I guess I'm not sure how to invoke the the action within the plugin? Where would I do that?

Reply
J-Tricks
7/18/2011 06:08:27 am

Joe, No problem!

What you have done here is just creating a new action. Now instead of using the standard JIRA group picker, you should use a customized one that invokes this action (SecureGroupBrowser) instead of the old JIRA action (GroupPickerBrowser). Does that make sense?

Reply
Joe Caputo link
7/18/2011 06:15:29 am

That does make sense, but I'm not exactly sure how to do that. I think that's my problem.

How do I create a customized JIRA Group Picker and invoke that action? What steps do I need to do in my plugin?

Thanks again for all your help. It really is very appreciated...

Reply
J-Tricks
7/18/2011 06:31:45 am

Checkout the edit template of your customfield and there must be a grouppicker macro used. Inside that macro, you will see the old action invoked. You will have to create a new macro that invokes the new action and use that macro in the customfield edit template!

Reply
Joe Caputo link
7/18/2011 06:51:20 am

OK, i'll give that a shot. Thanks so much.

Reply
Joe Caputo link
7/18/2011 06:28:06 pm

Just wanted to thank you for all your help. With your help, I've successfully created a plugin that does what I want it to do.

Thanks again,
-Joe

Reply
J-Tricks
7/18/2011 10:35:21 pm

@Joe That's awesome :)

Reply
Joe Caputo
11/15/2011 08:11:27 am

Hi there,

So by default, when adding a watcher to a ticket doesn't email the newly added watcher saying they have been added to as a watcher.

Could we simply override the ManageWatcher action and provide this functionality as well?

Thanks very much in advance.

Reply
J-Tricks
11/15/2011 10:10:53 am

@Joe, Yes, you can certainly do it. Following is what i would do.

1. Create a new event named 'Watcher Event'
2. Create the notification templates for it
3. Extend the ManageWatcher action and throw the new event after the watcher is added!

Reply
Joe Caputo link
11/16/2011 03:16:28 am

OK, I think that makes sense. :)

So, this is what I'm doing then:

I'm creating a new class that extends ManageWatchers and I'm going to override the method that adds selected users the watchers list.

In my atlassian-plugin.xml file, I'm going to create a webwork1 item and ensure that the alias for the action is the same action used for AddingWatchers (because I want to override that internal JIRA functionality):

<webwork1 key="ea-addwatchers" name="Electronic Arts Add Watchers Action" >
<actions>
<action name="com.ea.jira.plugins.actions.EAManageWatchers" alias="ManageWatchers">

I am then going to create 3 vm files and put in the corresponsding html, text and subject folders.

Then I'm going to add new entries to email-template-id-mappings.xml so when I'm creating a new event, those will be available?

My only question at this point is - how in code do I call that custom event? Do I have to create a new IssueMailQueueItem and add that item to the MailQueue's addItem method? Something along those lines? I want to be sure I can access baseURL, issueID, etc in the vm files.

Reply
Joe Caputo link
11/16/2011 03:49:24 am

It seems a bit tricky as I'm not sure how to get an instance of IssueEvent in my class?

Reply
J-Tricks
11/16/2011 05:52:05 am

Yes. the templates will be available once you add it.

Custom event can be thrown in the code using IssueEventDispatcher : http://docs.atlassian.com/jira/latest/com/atlassian/jira/event/issue/IssueEventDispatcher.html

Reply
Joe Caputo link
11/16/2011 07:04:29 am

Hmm, ok - trying using that Event Dispatcher. It seems to call it and I get no errors back in the log, but i never get an email. 10000 is my new event i created called "Watcher Added". here is the code:

if (isUserPermittedToSeeIssue(user))
{


HashMap<String,Object> map = new HashMap<String,Object>();
System.out.println(user.getEmailAddress());

NotificationRecipient recipient = new NotificationRecipient(user);



HashSet<NotificationRecipient> recipients = new HashSet();

recipients.add(recipient);
map.put("baseurl", ComponentManager.getOSGiComponentInstanceOfType(ApplicationProperties.class).getString(APKeys.JIRA_BASEURL));
System.out.println(ComponentManager.getOSGiComponentInstanceOfType(ApplicationProperties.class).getString(APKeys.JIRA_BASEURL));
MutableIssue issue = getIssueObject();


map.put("issue", issue);


//IssueEvent newEvent = new IssueEvent(issue, map, user, null);

//if(newEvent!=null) {

//System.out.println("newEvent is not null!");
//System.out.println(newEvent.getIssue().getKey());

//}


issueEventManager.dispatchEvent((long)10000, issue, map, user, true);






watcherManager.startWatching(user, getIssue());

Reply
J-Tricks
11/16/2011 07:11:09 am

You don't need a Map here. Use http://docs.atlassian.com/jira/latest/com/atlassian/jira/event/issue/IssueEventManager.html#dispatchEvent(java.lang.Long,%20com.atlassian.jira.issue.Issue,%20com.atlassian.crowd.embedded.api.User,%20boolean)

Also, make sure you are subscribed to your own changes. By default, you won't get notifications for your own changes!

Reply
Joe Caputo link
11/16/2011 07:14:55 am

OK, I'll try that one. Does it hurt to use the map version?

So here's my scenario. I'm logged in as admin and I'm adding the user joe to the watcher list. I want user Joe to be notified that he was added as a watcher...

So with the above, Joe (me) should be getting a notification right? Does not appear to be so.

Reply
J-Tricks
11/16/2011 07:17:27 am

That should definitely work!

Reply
Joe Caputo link
11/16/2011 07:26:46 am

Hmmm - yeah, it doesn't seem to be working. When I add the watcher, I get no errors whatsoever. They get added, but nothing else happens. What could it be?

Reply
Joe Caputo link
11/16/2011 07:36:03 am

Here is the full method body:

public String doStartWatchers() throws EntityNotFoundException, GenericEntityException
{
try
{
// Require the MANAGE_WATCHER_LIST permission to add other users to the watch list
if (!isCanEditWatcherList())
{
return "securitybreach";
}
}
catch (IssueNotFoundException e)
{
return ERROR;
}
catch (IssuePermissionException e)
{
return ERROR;
}

final Collection<String> userNames = UserPickerWebComponent.getUserNamesToAdd(getUserNames());
if (userNames.isEmpty())
{
addErrorMessage(getText("watcher.error.selectuser"));
return ERROR;
}

boolean badUsersFound = false;
for (final String userName : userNames)
{
final User user = getUser(userName);
String email = user.getEmailAddress();
if (user != null)
{
if (isUserPermittedToSeeIssue(user))
{


HashMap<String,Object> map = new HashMap<String,Object>();
//System.out.println(user.getEmailAddress());

//NotificationRecipient recipient = new NotificationRecipient(user);

HashSet<NotificationRecipient> recipients = new HashSet();

//recipients.add(recipient);
map.put("baseurl", ComponentManager.getOSGiComponentInstanceOfType(ApplicationProperties.class).getString(APKeys.JIRA_BASEURL));


MutableIssue issue = getIssueObject();


map.put("issue", issue);


//IssueEvent newEvent = new IssueEvent(issue, map, user, null);

//if(newEvent!=null) {

//System.out.println("newEvent is not null!");
//System.out.println(newEvent.getIssue().getKey());

//}


issueEventManager.dispatchEvent((long)10000, issue, user, true);

System.out.println(user.getEmailAddress() + " has been mailed!");



watcherManager.startWatching(user, getIssue());



}
else
{
badUsersFound = true;
addErrorMessage(getText("watcher.error.user.cant.see.issue", userName));
}
}
else
{
badUsersFound = true;
addErrorMessage(getText("watcher.error.usernotfound", userName));
}
}

if (badUsersFound)
{
setUserNames(null);
return ERROR;
}
else
{
return getRedirect("ManageWatchers!default.jspa?id=" + getId());
}
}

Reply
J-Tricks
11/16/2011 10:54:12 am

Is this line printed?

System.out.println(user.getEmailAddress() + " has been mailed!");

Also, you shouldn't be using the user object in dispatchEvent method. Instead you should use the currentUser (use your admin user for testing). All the people who subscribed to the event in notification schemes should get mail.

Also, disptachEvent is a static method in IssueEventDispatcher. So use IssueEventDispatcher.dispatchEvent(..)

Reply
Joe Caputo link
11/17/2011 06:08:01 am

Hey there,

I'm not sure if we're on the same page?

What we want is for example "joe" to be notified when another person adds them. I don't think we can use a notification scheme in this case. In the notification scheme, how would you specify the users that just got added to the watcher list? We don't want "All Watchers" to be notified for example, only the watchers that were just added.

And to answer your question about the line being printed. yep, that was printed. :)

Reply
Joe Caputo
11/17/2011 06:10:04 am

I have another version working but I'm not sure if this is a correct way to do things. But it seems to do what I want. If possible, I think I like your solution better and would like that to work instead. :)

Code:

HashMap<String,Object> map = new HashMap<String,Object>();

NotificationRecipient recipient = new NotificationRecipient(user);

HashSet<NotificationRecipient> recipients = new HashSet();

recipients.add(recipient);
map.put("baseurl", ComponentManager.getOSGiComponentInstanceOfType(ApplicationProperties.class).getString(APKeys.JIRA_BASEURL));

MutableIssue issue = getIssueObject();

map.put("issue", issue);

IssueEvent event = new IssueEvent(issue, map, user, (long)10000);


MailQueueItem item = issueMailQueueItemFactory.getIssueMailQueueItem(event, (long)99, recipients, "");

ComponentManager.getOSGiComponentInstanceOfType(MailQueue.class).addItem(item);

//issueEventManager.dispatchEvent((long)10000, issue, user, true);

watcherManager.startWatching(user, getIssue());

Reply
J-Tricks
11/17/2011 12:32:38 pm

Ok, I get it now. If you want only the watchers who are newly added to be notified, your code looks fine to me.

Throwing event will work only when you want to send notifications to people who are subscribed. Like all who want to know when new watchers are added!!

Reply
Joe Caputo link
11/18/2011 05:58:29 am

I really appreciate your help with all of this. Without you, I don't think I could get very far with some of these things. You are always pointing me in the right direction. :)

There is one more thing i need to ask:

I have a custom mail handler that does not allow a user to create issues via email. Only the ability to comment on an existing issue. I have the email working and the user gets a notification back saying they need to go to the url to submit a ticket, but the thing is, it seems my mail handler does not have access to some of the variables that are set in the header.vm and emailconstants.vm files. For example:

2011-11-18 10:38:49,467 Sending mailitem com.ea.jira.plugins.mail.EAUserMailQueu
eItem@7499d141[event=com.atlassian.jira.event.user.UserEvent@d724d375,subjectKey
=Origin Support,template=nocreateemail.vm,velocityRequestContextFactory=Multi-Te
nant proxy for Destroyed map,webResourceManager=Multi-Tenant proxy for Destroyed
map,event=com.atlassian.jira.event.user.UserEvent@d724d375,subjectKey=Origin Su
pport,template=nocreateemail.vm,subject=Origin Support,dateQueued=Fri Nov 18 10:
37:52 PST 2011,timesSent=0,mailThreader=<null>] ERROR ServiceRunner Mail Queu
e Service [velocity] RHS of #set statement is null. Context will not be modified
. templates/email/html/includes/emailconstants.vm [line 2, column 1]
2011-11-18 10:38:49,467 Sending mailitem com.ea.jira.plugins.mail.EAUserMailQueu
eItem@7499d141[event=com.atlassian.jira.event.user.UserEvent@d724d375,subjectKey
=Origin Support,template=nocreateemail.vm,velocityRequestContextFactory=Multi-Te
nant proxy for Destroyed map,webResourceManager=Multi-Tenant proxy for Destroyed
map,event=com.atlassian.jira.event.user.UserEvent@d724d375,subjectKey=Origin Su
pport,template=nocreateemail.vm,subject=Origin Support,dateQueued=Fri Nov 18 10:
37:52 PST 2011,timesSent=0,mailThreader=<null>] ERROR ServiceRunner Mail Queu
e Service [velocity] RHS of #set statement is null. Context will not be modified
. templates/email/html/includes/emailconstants.vm [line 3, column 1]

Reply
Joe Caputo link
11/18/2011 06:13:43 am

Oh, and just for your information, I'm using a UserMailQueueItem in this case because an issue doesn't exist at this point (therefore I can't use IssueMailQueueItem).

Reply
Joe Caputo link
11/18/2011 07:55:29 am

I'm wondering if i need to add something to the map in order to gain access to those velocity variables?

String to = user.getEmailAddress();

Map<String, Object> velocityParams = new HashMap<String, Object>();
velocityParams.put("content", "");
velocityParams.put("webResourceManager", webResourceManager);
velocityParams.put("baseurl", velocityRequestContextFactory.getJiraVelocityRequestContext().getBaseUrl());
velocityParams.put("urlModeAbsolute", UrlMode.ABSOLUTE);
velocityParams.put("padSize", PADSIZE);
velocityParams.put("context", context);
VelocityRequestContext velocityRequestContext = new DefaultVelocityRequestContextFactory(ComponentAccessor.getApplicationProperties()).getJiraVelocityRequestContext();

String body = ComponentAccessor.getVelocityManager().getEncodedBody(EMAIL_TEMPLATES, "html/" + template, (String)event.getParams().get("baseurl"), applicationProperties.getString(JIRA_WEBWORK_ENCODING), velocityParams);
Email email = new Email(to);
email.setFrom("someemail.com");
email.setSubject("Origin Support System Auto-reply");
email.setMimeType("text/html");
email.setEncoding(ENCODING_UTF8);
email.setBody(body);

ManagerFactory.getMailQueue().addItem(new SingleMailQueueItem(email));

Reply
Joe Caputo link
11/21/2011 03:37:19 pm

I figured it out sir. All good. :) Thanks very much for your time yet again!

Reply
Joe Caputo link
12/5/2011 08:18:53 am

Hi there!

How difficult would it be and if possible, I would like to change the options available when restricting a comment. I would like to ensure only the developer shows up in that list of items. Is this possible? And if so, where do I need to make this change. I'm assuming it's going to be in some velocity template, but I'm unsure as to which one. Thank you very much for your time.

Reply
J-Tricks
12/6/2011 03:00:41 am

@Joe Do you mean only the developer role shows up? The list is indeed coming from a VM. The original VM is WEB-INF/classes/templates/jira/issue/field/comment-edit.vm.

But it ultimately uses the following macro in WEB-INF/classes/templates/jira/global.vm.

#macro (createLevelSelect $roleLevels $groupLevels $selected)

You can just modify the macro to show only developer role. This might be used by work logs also. So modify accordingly.

Reply
Joe Caputo link
12/6/2011 11:16:59 am

Beautiful! I'll give it a shot!

Reply
Joe Caputo link
12/7/2011 07:14:30 am

Here's an interesting one. This is in regards to the the watcher stuff you helped me out with a little while back.

I'm trying to use the current person who executed the event to be used in the notification.

In my java class, I put a variable in the map like so:

User currentUser = ComponentManager.getInstance().getJiraAuthenticationContext().getUser();

System.out.println(currentUser.getEmail());

map.put("current", currentUser);

In my velocity, I have this:

#set ($changelogauthorLink = "#authorlink2($current.name $linkstyle)")


#set ($issueType = $issue.getIssueTypeObject())

#set ($issueLink = "#renderIcon(${issueType.iconUrlHtml} ${issueType.getNameTranslation($i18n)}) <a style='color:${textLinkColour};text-decoration:none;' href='${baseurl}/browse/${issue.getKey()}'>$issue.getKey()</a>")

<p style="font-size: 14px; font-family: arial;">$changelogauthorLink has added you as a watcher to this issue $issueLink</p>

However, when I get the email back from Java, it always has this:

Unassigned has added you as a watcher to this issue SomeIssue

Why is it always unassigned?

Thanks very much in advance.

Reply
Joe Caputo link
12/7/2011 09:44:33 am

Actually, it looks like it doesn't recognize that variable at all. This is what I get in the email. :(

$current.name has added you as a watcher to this issue TESTJOEC-1

Reply
Joe Caputo link
12/7/2011 10:00:23 am

I'm not sure what I'm doing wrong, but for reference, here is part of the java code that then fires off the mailqueueitem.

It seems my map values are not being sent to the velocity for one reason or another.

HashMap<String,Object> map = new HashMap<String,Object>();

NotificationRecipient recipient = new NotificationRecipient(user);

HashSet<NotificationRecipient> recipients = new HashSet();

recipients.add(recipient);
map.put("baseurl", ComponentManager.getOSGiComponentInstanceOfType(ApplicationProperties.class).getString(APKeys.JIRA_BASEURL));

MutableIssue issue = getIssueObject();

System.out.println(issue.getKey());

map.put("issue", issue);

User currentUser = ComponentManager.getInstance().getJiraAuthenticationContext().getUser();

System.out.println(currentUser.getEmail());

map.put("current", currentUser);

map.put("Joe", "Caputo");

IssueEvent event = new IssueEvent(issue, map, user, (long)10000);

System.out.println(event.getIssue().getSummary());

MailQueueItem item = issueMailQueueItemFactory.getIssueMailQueueItem(event, (long)99, recipients, "");

ComponentManager.getOSGiComponentInstanceOfType(MailQueue.class).addItem(item);

//IssueEventDispatcher.dispatchEvent((long)10000, issue, user, true);

getWatcherManager().startWatching(user, getIssue());

Reply
J-Tricks
12/7/2011 11:04:27 pm

@Joe The code looks okay. So the current user email is getting printed correctly in the System.out?

And what is the authorlink2 doing? Did you give directly as $current.name ?

Reply
Joe Caputo link
12/8/2011 04:28:42 am

Hi there,

The #authorlink2 macro was there before so I just continued to try and use it. I also tried just using $current.name and that just gets rendered as $current.name in the mail message. Weird.

Yes, the email address of the person adding the watcher is being displayed correctly. It just seems like that variable I'm passing into the map is not recognized by the velocity template. :(

Any ideas?

Reply
Joe Caputo link
12/8/2011 04:35:49 am

To clarify, when I use the #authorlink2 macro, in the mail sent by JIRA, i get:

"Unassigned has added you as a watcher, etc, etc". Most likely because it doesn't know what that variable is and that macro returns "Unassigned" if it can't find a match?

When I use $current.name directly in the message, I get:

"$current.name has added you as a watcher, etc, etc"

Reply
J-Tricks
12/8/2011 04:46:54 am

Strange! Can you just try $remoteUser.name and see how it goes?

Reply
Joe Caputo link
12/8/2011 04:55:40 am

I have tried that as well. :)

When I use remoteUser.name, then somethign weird happens.

If I get added as a watcher for example, it'll say:

Joe Caputo has added you as a watcher, even though somebody else added me. This goes for everybody else too. People then get confused because they actually have not added themselves as watchers. Why would remoteUser be equal to the user being added as a watcher in this case?

Reply
J-Tricks
12/10/2011 02:28:48 am

@Joe Maybe it is the behavior of MailQueueItem. I am guessing it takes the recipient name as current user!

I would like to go with notification schemes wherever possible. In that case you could have gone with dispatchEvent and I am sure that will take the current user properly. But I understand that you want to send mail to only the new user who is added as watcher.

Maybe you can has a hidden multi-user custom field which is populated when a watcher is added and use that in the notification schemes ;) The only issue I can think of is when 2 users do it simultaneously but how many time does that happen?

Reply
mizan
12/19/2011 04:20:24 pm

Hi,
I am trying to create a plugin which changes its status when a file is attached to it but now i am just trying print a message on the console when i attach a file to a issue.

I am trying to achieve this by extending jira action "AttachFile".

However i dont get desired output and i get a message on the console

[INFO] [talledLocalContainer] 2011-12-20 10:29:59,390 http-2990-6 INFO admin 629
x548x3 jq8q6o 127.0.0.1 /rest/api/1.0/menus/find_link [plugin.util.resource.Alte
rnativeDirectoryResourceLoader] Found alternative resource directory E:\mizan\sr
c\main\resources

Is there something which i am missing ?

thanx :)

Reply
J-Tricks
12/19/2011 11:52:37 pm

@mizan Can you please attach your atlassian-plugin.xml contents here?

Reply
mizan
12/20/2011 05:17:21 pm

Hi,
I was doing a silly mistake. I got the desired output.
i had not change the action name in the atlassian-plugin.xml it was same as it is in the actions.xml . when i changed the name to my class it worked.
Thanx :)

Reply
mizan
12/20/2011 05:24:48 pm

Hi,
Is it possible to get the Mutable issue Object in the Class which extends AttachFile ?
Because when i create a mutable issue object the value for it is null even after i attach a file to the issue.
because of this i am not able to change the status of the issue.
Can you please guide me on this ?
Thanx :)

Reply
J-Tricks
12/21/2011 12:40:28 am

@mizan You should be getting the issue key in the class right? Use IssueManager to get the issue Object using the key!

getKey() will give you the key if you are extending the AttachFile action.

Reply
mizan
12/21/2011 03:46:34 pm

i was using " MutableIssue issue = ComponentManager.getInstance().getIssueFactory().getIssue(); " to get the issue instance when i replaced this by " MutableIssue issue1=getIssueObject(); " the plugin works.

Thanx :)

Reply
J-Tricks
12/22/2011 03:13:58 am

Awesome :)

Lars Broden link
3/4/2012 10:28:27 am

Hi
I have downloaded the code for "Extending JIRA Actions" above and imported into JIRA, changed to the POM to contain the right versions etc.., but I cant seem to be able to get the MyCreateIssueDetails.java class to fire?
I am running on a JIRA 5.0 dev installation and the build goes through properly through the atlas-package & atlas-debug commands but the do validation does never fire when I set a breakpoint to it?, any suggestions?

Reply
J-Tricks
3/4/2012 02:51:18 pm

Lars, Are you using the Quick Create form on the right hand top to create the issue? if so, it doesn't fire the CreateIssueDetails action. Instead it fires QuickCreateIssue.jspa. In fact, in JIRA5, it looks like all the Create options fire the above action.

You can find the action to override by looking at action fired using Firebug in firefox.

Reply
Lars Broden link
3/5/2012 10:00:48 am

Sorry to be a pain but I cant get it to work. Yes I am using the quick create option on the upper right corner of the form, and scanning through the jsp files in the secure views folder I dont find a match of the QuickCreate jsp? , the only files that I could see applicable for the webaction is createissue start jsp or the createissue details, but none of them fires the webaction while doing a quick create

Reply
J-Tricks
3/5/2012 02:19:55 pm

Lars,

It appears that the Quick Create form is done using AJS dialogs and not via any JSPs. You wouldn't be able to override them as mentioned in this post. This is due to the changes in 5.0.

What exactly are you trying to do? There might be an alternative way?

Reply
Lars Broden
3/5/2012 06:39:59 pm

Ok, that explains why..
What I am trying to accomplish thought which I thought your code snippet could be usefult was to "pre-populate" som blank fields with preset values at the time you choose quickcreate. As an example I thought to use the overriding class createIssueDetails | doInit() to set the description field with a prepoulated value something like getIssueObject().setDescription("||My Heading 1 | My Heading 2 |")
(Assuming in this particular example that the wikirendering is swithced on to the description field)
Any ideas?

J-Tricks
3/6/2012 12:31:22 am

@Lars I see. You can do that with simple Javascript as explained in http://www.j-tricks.com/1/post/2012/02/some-ajs-tricks.html. It shows how to populate description and you can omit the other things like checking for user/groups.

Hope it helps!

Reply
Lars Broden
3/6/2012 08:44:33 am

Well... even if the script does the trick I am wondering if this might be the right architected solution for my purpose? I was thinking more along the lines of a pluign with a associated UI where you can choose (Wiki rendered free text fields only) which field(s) you want to pre-populate with what and for which projects/forms it should have an impact so that when you choose "quick create" later regardless on which project youre in it would do the correct pre-population based on your earlier settings. The override function for createissue-details.jsp seemed promising, is there a similiar substitute in JIRA 5, that could be used instead?,

Reply
J-Tricks
3/6/2012 09:46:06 am

Unfortunately, I haven't seen a better way to do that in JIRA5. Atlassian themselves uses Javascript and there is no other means to override it!

You might want to have a look at https://studio.plugins.atlassian.com/wiki/display/JBHV/JIRA+Behaviours+Plugin to see how the plugin leverages Javascript to do similar stuff.

Adam
3/26/2012 10:49:30 am

Hi,

I am trying to extend ViewIssue with JIRA5. I have extended the ViewIssue class with ExtendedViewIssue and I have added a webwork module in atlassian-plugin.xml called view-issue with basically the same contents as with the actual view action except I have modified the "success" view with my copy of /secure/views/issue/extended-viewissue.jsp.

Below is the error I am receiving:
java.lang.ClassCastException: $Proxy458 cannot be cast to com.atlassian.jira.plugin.webresource.JiraWebResourceManager
at com.atlassian.jira.web.action.issue.ViewIssue.doExecute(ViewIssue.java:149)
at webwork.action.ActionSupport.execute(ActionSupport.java:165)

Any ideas? Thanks!

Reply
Fidel
12/4/2012 10:03:54 pm

I'm getting the same error. Any clue about what's happening?

Reply
J-Tricks
12/5/2012 04:11:53 am

Looks like ClassCastException at ViewIssue.doExecute(ViewIssue.java:149)? What is in there?

Fidel
1/10/2013 10:44:07 pm

Problem solved!!! I was injecting a "WebResourceManager" in "ViewIssue" constructor, according to the declaration of this interface. Nevertheless, implementation of method doExecute() requires a "JiraWebResourceManager", which is a subinterface of "WebResourceManager".

The solution is injecting in the constructor a "JiraWebResourceManager" instead of a "WebResourceManager".

public ExtendedViewIssue(
SubTaskManager subTaskManager,
PluginAccessor pluginAccessor,
FieldManager fieldManager,
FieldScreenRendererFactory fieldScreenRendererFactory,
FieldLayoutManager fieldLayoutManager,
RendererManager rendererManager,
CommentManager commentManager,
ProjectRoleManager projectRoleManager,
CommentService commentService,
JiraWebResourceManager jiraWebResourceManager,
SimpleLinkManager simpleLinkManager,
WebInterfaceManager webInterfaceManager,
PermissionManager permissionManager,
ModuleWebComponent moduleWebComponent,
UserUtil userUtil,
FeatureManager featureManager,
AvatarService avatarService,
EventPublisher eventPublisher,
ApplicationProperties applicationProperties,
UserPickerSearchService userPickerSearchService)
{
super(subTaskManager,
pluginAccessor, fieldManager,
fieldScreenRendererFactory,
fieldLayoutManager,
rendererManager,
commentManager,
projectRoleManager,
commentService,
ComponentAccessor.getComponentOfType(PagerManager.class),
jiraWebResourceManager,
simpleLinkManager,
webInterfaceManager,
permissionManager,
moduleWebComponent,
userUtil,
featureManager,
avatarService,
eventPublisher,
applicationProperties,
userPickerSearchService);
}

Nikita
5/21/2014 09:18:12 pm

How did you migrate you plugin to jira 6.2? Extending of ViewIssue is not possible now : (

Reply
J-Tricks
5/22/2014 01:28:40 am

Nikita,

You are right. Atlassian has moved that logic to a bundled plugin and it is not possible to extend that action using the above technique. You can only modify the bundled plugin (http://www.j-tricks.com/tutorials/modifying-atlassian-bundled-plugins).

Ana
5/25/2012 01:43:17 am

Hello! I have read the comments on this page and I find it very useful!! I'm new at using Jira and I don't know if there is a possibility of associate a list of objets of a certain type to an issue. For example, I could have a list of "Implementation" objects associated to an issue. Each object could have a Date, State and Client name attributes (we can have the same issue for various customers). The type of the fields Jira offers is a simple data-type (like date, text...) but I need a type kind of "Object". Do I need to develop a plugin? Is it possible to make this with Jira?
Thanks a lof for your help!
Kind Regards,
Ana

Reply
Tom
2/27/2013 01:03:33 pm

Hi JTricks. Awesome stuff.

My issue is that I need to be able to automatically change the status of newly created tickets based on whether or not they have been given an Assignee.

example: if the parent ticket has an Assignee then that ticket is automatically transitioned to the next status, the same goes for it's sub tasks.

However if the ticket does NOT have an assignee, then the ticket remains at the Open status.

Current workflow is Create Ticket > Open > Status1 > etc...

Thankyou for your help or guidance. The answer may exist somewhere but my search has not surfaced a solution. help me Kenobi.

Reply
J-Tricks
2/27/2013 03:08:32 pm

In your case, extending actions will not help. You will have write a listener that captures the new issues and progress them in the workflow based on assignee.

This might be useful : https://jamieechlin.atlassian.net/wiki/display/GRV/Built-In+Scripts#Built-InScripts-Fast-tracktransitionanissue

Reply
Sathish
9/5/2013 08:20:31 pm

Hi,
There is a bug in Jira 5.2 that we cannot install a custom mail handler. The webaction framework will not be able to display configuration parameter and throws the exception given below. Basically I have used the mail handler demo example code and used it as template to fill my custom functionality. I do not need validation and custom configuration. I wanted to know whether add-edit-url can be removed? Basically I just want my custom email handler alone registered and run. Can you please help me here?

Exception:
[jira.web.dispatcher.JiraWebworkActionDispatcher] Exception thrown from action 'EditSFCustomEmailHandlerDetails!default', returning 404
java.lang.NoClassDefFoundError: com/atlassian/jira/plugins/mail/webwork/AbstractEditHandlerDetailsWebAction
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(Unknown Source)
Thanks
Sathish

Reply
Fhatu
3/3/2014 07:43:32 pm

Hi,


I'm using an issue-tabpanel, having two classes one thats extend AbstractIssueTabPanel and another extends AbstractIssueAction. I am able to pass data to velocity template and display it.

Now my problem is to get the data from the text field in the velocity template to my java method and process it.

Any advice how i can archive that ?

Reply
J-Tricks
3/6/2014 03:34:50 pm

You can get the value of the text field in the action class by declaring a private variable of the same name and then by defining setter methods for it. Is that what you are looking for?

Reply
Fhatu
3/6/2014 05:50:14 pm

I extended jirawebsupport from another class and it worked

вакансии в рязани бухгатера link
5/14/2014 12:47:49 pm

Hi my friend! I want to say that this post is awesome, good written and incorporate almost all significant infos. I’d like to see more posts like this .

Reply
J-Tricks
5/22/2014 01:26:36 am

Glad you liked it :)

Reply
Kapil Bolli
8/11/2014 03:05:48 am

Hi JTricks,

I had used Jira CookBook for JIRA 5.x and developed a plugin for extending JIRA actions on view - "Displaying dynamic notifications/warnings on issues"

Now i am migrating to JIRA 6.2.7 ,
I have seen your comment as the view issue is controlled in bundled plugins.Can you help me in finding the replacement of viewissue.jsp in JIRA 6.2.7
So that i can update my plugin.

-Thanks
Kapil

Reply
J-Tricks
8/11/2014 12:42:31 pm

The view issue page is now broken down into different templates in the jira-view-issue-plugi. Take a look at http://www.j-tricks.com/1/post/2012/05/modifying-atlassian-bundled-plugins.html for example.

Reply
Gustavo
9/23/2014 03:06:14 am

Hello!

Can I extend a jira action using a jsp view inside my resource folder?
That way, in case of a upgrade, I would just install the plugin.

I'm using plugins-version 2.

This way, pointing to a existing file in the installation folder, works:
<action name="[my namespace].testWorklog" alias="CreateWorklog">
<view name="error">/secure/views/issue/logwork.jsp</view>
<view name="input">/secure/views/issue/logwork.jsp</view>
<view name="securitybreach">/secure/views/securitybreach.jsp</view>
</action>

But I would not change those files, I rather point to my own custom jsp.
The file I want to use is located under my resources folder under "views.custom"
Something like this:
<action name="[my namespace].testWorklog" alias="CreateWorklog">
<view name="error">[my plugin path]/logwork.jsp</view>
<view name="input">[my plugin path]/logwork.jsp</view>
<view name="securitybreach">/secure/views/securitybreach.jsp</view>
</action>

I tried many options on [my plugin path], but no success.
I got no error on the logs, just the message:
The JIRA server could not be contacted. This may be a temporary glitch or the server may be down.
Close this dialog and press refresh in your browser

So, what should I use as [my plugin path]? Any ideas?

Thanks!

Reply
J-Tricks
9/23/2014 04:05:58 am

Nope, JSPs cannot be added in the plugin. They have to be dropped in the webapp.

Reply
Gustavo
9/23/2014 05:11:37 am

Ok. So on every upgrade I will install the plugin and then move the file to jira folder? Is there a good way to do that or just strictly manual?

Thanks again!

J-Tricks
9/23/2014 05:46:32 am

Unfortunately, that is the only option now. Manual, unless you have something like chef or puppet to do that for you.

Diego link
10/24/2014 09:42:41 am

Hello

I´m trying to implement this sample and did not work :(
I´m using Jira 6.0.7, I need to create a webitem that open the create issue dialog with some pre filled fields, specially the link issue field in order to create and link with the issue that was in the screen when the user click in the web item. Is that possible? i´m ok trying to use this sample or I have to change the way to solve it?.

Thanks in Advance
Regards

Reply
J-Tricks
10/24/2014 01:39:57 pm

Take a look at https://developer.atlassian.com/display/JIRADEV/Displaying+Content+in+a+Dialog+in+JIRA. That seems to be the best fit for your need!

Reply
Parag link
3/31/2016 01:27:17 am

When calling a action method using alias e.g. /secure/upraise/objectives/ObjectiveAction!ListAll.jspa there is no need of part of the uri "/upraise/objectives/", with or without this it works. But if I want to add such a namespace is is possible? otherwise if some other plugin defines "ObjectiveAction", this one gets overridden.
If there is no option, then am I right in assuming action aliases should also be unique across JIRA?

Reply
J-Tricks
4/2/2016 07:58:31 am

Yes, the alias has to be unique.

Reply

Your comment will be posted after it is approved.


Leave a Reply.

    Enter your email address:

    Author

    Jobin Kuruvilla - Works in Adaptavist as Head of DevOps Professional Services. 

    Author of JIRA Development Cookbook and JIRA 5.x Development Cookbook.


    RSS Feed

    Categories

    All
    Acive Objects
    Ajs
    Book
    Components
    Condition
    Custom Fields
    Customization
    Events
    Gadgets
    Javascript
    Jql
    Listener
    Mail
    Permissions
    Plugin Framework
    Post Function
    Properties
    Remote Invocation
    Reporting
    Rest
    Scheduled Tasks
    Search
    Services
    Soap
    Summit
    User Interface
    Validator
    Webwork Actions
    Workflow

    Archives

    October 2016
    August 2016
    March 2016
    January 2016
    December 2015
    May 2014
    December 2013
    November 2013
    July 2013
    June 2013
    April 2013
    October 2012
    September 2012
    August 2012
    July 2012
    May 2012
    March 2012
    February 2012
    January 2012
    December 2011
    November 2011
    June 2011
    May 2011
    April 2011
    March 2011
    February 2011
    January 2011
    November 2010
    October 2010
    September 2010
    August 2010

SUPPORT
APPS
TUTORIALS
THE BOOK
© J-Tricks