dannyman.toldme.com


JIRA, Technical

Embed Page Refresh

Link: http://dannyman.toldme.com/2013/05/13/embed-page-refresh/

Feature request that certain JIRA dashboards should reload more frequently than every fifteen minutes. So, I cooked up some JavaScript to hide in the announcement banner:

<script type="text/javascript">
 
function gup( name ){
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
 if( results == null )    return "";
else    return results[1];}
 
function reload() {
 window.location = window.location
}
 
var refresh = gup('refresh');
if ( refresh > 0 )    window.setInterval(reload, refresh*1000);
</script>

Now users can add refresh=nn and the page will reload every nn seconds. This ought to work in most cases where you can sneak some HTML into a Web App.

Function gup stolen from http://stackoverflow.com/questions/979975/how-to-get-the-value-from-url-parameter.

Feedback Welcome


JIRA

JIRA Cascading Select in Jython

Link: http://dannyman.toldme.com/2012/12/06/jira-cascading-select-in-jython/

The Cascading Select Custom Field type in JIRA is a bear. The first trick is learning to set the “null” value and then the “1″ child value. The next trick is building out a ModifiedValue object to hold your change. Then you get to jump down the rabbit hole of finding the correct Option values for the custom field, and setting them with the tricks just mentioned.

So, in the interests of saving me sanity next time I need to set a Cascading Select, here’s a Jython function that works in Jira 4.2:

import logging
 
from com.atlassian.jira import ComponentManager
from com.atlassian.jira.issue.customfields.manager import OptionsManager
from com.atlassian.jira.issue.customfields.view import CustomFieldParamsImpl
from com.atlassian.jira.issue import ModifiedValue
from com.atlassian.jira.issue.util import DefaultIssueChangeHolder
from java.util import HashSet
 
# cf = custom field
# issue = issue to modify
# parent = top value to set (string value)
# child = child value to set (string value)
def set_cascading_select(cf, issue, parent, child):
    # Get the managers
    cfm = ComponentManager.getInstance().getCustomFieldManager()
    om = ComponentManager.getComponentInstanceOfType(OptionsManager)
    fli = ComponentManager.getInstance().getFieldLayoutManager().getFieldLayout(issue).getFieldLayoutItem(cf)
 
    parent_options = om.getOptions(cf.getRelevantConfig(issue))
    parent_option = None
    child_option = None
    try:
        parent_option = parent_options.getOptionForValue(parent, None)
    except:
        pass
    try:
        child_option = parent_options.getOptionForValue(child, parent_option.getOptionId())
    except:
        pass
 
    if parent_option and child_option:
        old_application = issue.getCustomFieldValue(cf)
        new_application = CustomFieldParamsImpl(cf)
        a_none = HashSet()
        a_none.add(parent_option)
        a_1 = HashSet()
        a_1.add(child_option)
        new_application.put(None, a_none)
        new_application.put("1", a_1)
        mf = ModifiedValue(old_application, new_application)
        cf.updateValue(fli, issue, mf, DefaultIssueChangeHolder())
        logging.debug("set issue " + issue.getKey() + " cf " + cf.getName() + " setting " + parent + "/" + child)
        return True
    else:
        logging.error("invalid parent/child option: " + parent + "/" + child)
        return None

Example function calls from within a validation hook:

cfm = ComponentManager.getInstance().getCustomFieldManager()
 
application_cf = cfm.getCustomFieldObjectByName("Beverages")
 
# good
set_cascading_select(application_cf, issue, "Hard Drinks", "Whiskey")
# bad child
set_cascading_select(application_cf, issue, "Hard Drinks", "Coke")
# bad parent
set_cascading_select(application_cf, issue, "Soft Drinks", "Whiskey")
# total crap
set_cascading_select(application_cf, issue, "Illicit Drugs", "Bath Salts")

The logging stuff is useful for debugging, if you have that set up, else just remove those bits.

Feedback Welcome


JIRA, Technical

JIRA Workflow Transition Condition: check_parent_resolved.py

Link: http://dannyman.toldme.com/2012/11/07/jython-workflow-transition-check-parent/

It took a few hours to figure this hook out, so I’m including my hard-won lines of code here.

# -*- coding: UTF-8 -*-
# Check if PARENT is resolved.
# Monitoring creates Events in the Event queue, these Events
# automatically create Incident children.
# We don't want to resolve any Incident children until the parent Event
# resolves.
# 
# (Normally you want to block on your children instead of your parent.)
 
from com.atlassian.jira import ComponentManager
from com.atlassian.jira.issue.link import IssueLinkManager
 
ilm = ComponentManager.getInstance().getIssueLinkManager()
 
# Assume we are okay ...
result = True
 
for link in ilm.getInwardLinks(issue.getId()):
    if link.getIssueLinkType().getName() == "Parent" and link.getSourceObject().getResolution() == None:
        result = False

Feedback Welcome


JIRA, Python, Technical

Jython Validator Cookbook

Link: http://dannyman.toldme.com/2012/09/25/jython-validator-cookbook/

Building on a previous post to validate user time tracking, a few “cookbook” scripts that may be handy to you or me in the future.

JIRA 4.2, YMMV.

Validate User Time Tracking

Somewhat elaborate: enforce that time worked has been logged, except under certain circumstances. See original post.

import com.atlassian.jira.issue.worklog.Worklog
from com.atlassian.jira import ComponentManager
 
# Time Already Logged
timespent = issue.getTimeSpent()
# Time Logged via current screen
try:
    timelogged = dict(issue.getModifiedFields())['worklog']
except:
    timelogged = False
 
# Duplicate Issue?  It is as good as logged!
resolution = issue.getResolution()
if resolution['name'] == "Duplicate":
    timelogged = True
if resolution['name'] == "Self Corrected":
    timelogged = True
 
# Nagios likes to close tickets, but doesn't get paid
user = ComponentManager.getInstance().getJiraAuthenticationContext().getUser()
if user.getName() == "nagios":
    timelogged = True
 
if timespent < = 0 and timelogged == False:
    result = False
    description = "Please log the time you spent on this ticket."

Assign Unassigned Issue to User

This helps make sure tickets get assigned.

# -*- coding: UTF-8 -*-
from com.atlassian.jira import ComponentManager
 
assignee = issue.getAssignee()
user = ComponentManager.getInstance().getJiraAuthenticationContext().getUser()
 
if not issue.getAssignee():
issue.setAssignee(ComponentManager.getInstance().getJiraAuthenticationContext().getUser())

Validate Custom Field Value

We have a particular custom field which can be set UNKNOWN by the Reporter, but which should be cleaned up by the Assignee.

from com.atlassian.jira import ComponentManager
 
cfm = ComponentManager.getInstance().getCustomFieldManager()
product = issue.getCustomFieldValue(cfm.getCustomFieldObject('customfield_12345'))
 
if product == 'UNKNOWN':
result = False
description = "Please set CUSTOM_FIELD value appropriately."

Feedback Welcome


FreeBSD, JIRA, Linux, Mac OS X, Technical

Strip Non-Ascii From a File

Link: http://dannyman.toldme.com/2012/05/10/mysql-not-configured-for-utf8/

I have had bad luck trying to coax this out of Google, so here’s a Perl one-liner:

perl -pi -e 's/[\x80-\xEF]//g' file.txt

Where file.txt is a file you want to clean up.

Why this comes up is because we have a web application that was set up to hit a MySQL database, which is incorrectly configured to store text as ASCII instead of UTF-8. The application assumes that all text is Unicode and that the database is correctly configured, and every week or two someone asks me why they are getting this weird gnarly error. Typically they are pasting in some weird UTF-8 whitespace character sent to us from Ukraine.

Eventually the database will be reloaded as UTF-8 and the problem will be solved. Until then, I can tell folks to use the Perl command above. It just looks for anything with the high bit set and strips it out.

Feedback Welcome


JIRA, Technical

Work Note: Quarterly Reports from MySQL

Link: http://dannyman.toldme.com/2012/05/08/jira-sla-sql-query/

I am not a DBA. I am but a humble SysAdmin who gets asked to figure out things like “how have we been at meeting our SLAs over time?” After I try to excuse myself I’ll inevitably end up say at the JIRA database running a query like this:

echo
echo "Incidents (P3)"
mysql -u jira jiradb< <__id3q
select year(created) as "Year", quarter(created) as "Quarter",
    count(pkey) as "Total",
    sum(resolutiondate < date_add(created, interval x day)) as "Met SLA",
    sum(resolutiondate < date_add(created, interval x day)) / count(pkey) as "SLA %%"
    from jiraissue where pkey like 'OPS-%' and priority = 3
    and assignee != 'nagios' and issuetype = 26
    group by year(created), quarter(created) order by created;
__id3q

That above is a fragment from a shell script. Shell scripts are great for complex SQL queries, I find. Set a value x at interval x day and the output looks something like:

Incidents (P3)
Year    Quarter Total   Met SLA SLA %%
2011    2       xxx     xxx     x.xxxx
2011    3       xxx     xxx     x.xxxx
2011    4       xxx     xxx     x.xxxx
2012    1       xxx     xxx     x.xxxx
2012    2       xxx     xxx     x.xxxx

The query does some things that are newer to my limited understanding of SQL. For me the magic bits are sum()ed columns and the availability of quarter() … you can do monthly reports just as easily with month(). I’d love to concatenate Year-Month into a string like “2012-05″ but for the purposes of making my boss a little happier queries like this are good to have in the locker.

Some day I’ll be hip enough to convert things like this into JIRA widgets.

Oh yeah, and if your SLAs are measured in “business hours” or “business days” this will give you only a crude understanding of how well you have met your SLAs … an accurate measure would probably get embedded in a handler that gets called on issue close which can evaluate SLA fulfillment per issue priority and the local work schedule.

1 Comment


JIRA

JIRA: Require User Time Tracking

Link: http://dannyman.toldme.com/2011/12/01/jira-jython-validator-enforce-time-spent/

Time tracking in JIRA is a nice feature, but we have to get people to do it. My initial attempts to enforce time tracking ran into trouble, but I was able to develop a Jython Validator to hook on to transitions to the Resolved state. Now it is mandatory for our users to log time worked before they can resolve an issue:

# -*- coding: UTF-8 -*-
import com.atlassian.jira.issue.worklog.Worklog
from com.atlassian.jira import ComponentManager
 
# Time Already Logged
timespent = issue.getTimeSpent()
# Time Logged via current screen
try:
    timelogged = dict(issue.getModifiedFields())['worklog']
except:
    timelogged = False
 
# Duplicate Issue?  It is as good as logged!
resolution = issue.getResolution()
if resolution['name'] == "Duplicate":
    timelogged = True
if resolution['name'] == "Self Corrected":
    timelogged = True
 
# Nagios likes to close tickets, but doesn't get paid
user = ComponentManager.getInstance().getJiraAuthenticationContext().getUser()
if user.getName() == "nagios":
    timelogged = True
 
if timespent < = 0 and timelogged == False:
    result = False
    description = "Please log the time you spent on this ticket."

2012-01-24 Update: the script now contains additional logic, which exempts the nagios user from enforcement and allows resolution of duplicated or self-correcting issues which may not require time tracking. Hopefully this example is useful to somebody.

3 Comments


JIRA, Sundry, Technology

JavaScript Hack: Hide an Element on a Page

Link: http://dannyman.toldme.com/2011/10/25/javascript-hack-hide-an-element-on-a-page/

JIRA is an issue tracking system that is really flexible, but sometimes presents irritatingly arbitrary limitations.

I have been working on a screen which uses multiple tabs. The tabs are there to make it easier for the user to find the fields they want to edit, without scrolling through a single long, complex issue. But every tab has a Comment field rendered on it, which makes things confusing, and makes each tab look like it needs scrolling.

So, just remove the Comment field from the Screen, right? No, it isn’t in there. So, can I remove Comment via the Field Configuration Scheme? No, it is mandatory. Damn your arbitrary limitation, JIRA!

Anyway, I don’t normally speak JavaScript, but I managed to gin up the following snippet to paste into a Field description which appears in the screen I wanted to tweak. It finds the element containing the Comment, and sets its style display attribute to none. As the page loads, the Comment box is rendered, but once the page load completes, the Comment box disappears.

<script type="text/javascript">
function hideCommentField() {
        var elements = document.getElementsByClassName('field-group aui-field-wikiedit');
        elements[0].style.display = 'none';
}
// http://stackoverflow.com/questions/807878/javascript-that-executes-after-page-load
if(window.attachEvent) {
    window.attachEvent('onload', hideCommentField);
} else {
    if(window.onload) {
        var curronload = window.onload;
        var newonload = function() {
            curronload();
            hideCommentField();
        };
        window.onload = newonload;
    } else {
        window.onload = hideCommentField;
    }
}
</script>

It is ugly, but effective. Also, it is helpful for me to learn JavaScript!

PS: Thanks for the Guidance, Ed Burns!

1 Comment


doodles, JIRA, Technical, Technology

TICKET. OR. GTFO.

Link: http://dannyman.toldme.com/2011/08/17/ticket-or-gtfo/

I do not know the provenance of the source material, and can make no claims of intellectual property rights here. TinEye finds 550 similar images.

2 Comments


Site Archive