JIRA Time Log Automation

Log work in JIRA from xls or slimtimer by reading task tags.

// ==UserScript==
// @name           JIRA Time Log Automation
// @namespace      com.spiderlogic
// @description    Log work in JIRA from xls or slimtimer by reading task tags.
// @include        http*://*slimtimer.com/edit*
// @include        http*://*.jira.com/browse/*
// @include        http*://*.jira.com/secure/CreateWorklog!default.jspa*
// @include        http*://*/jira/browse/*
// @include        http*://*/jira/secure/CreateWorklog!default.jspa*
// @version 0.0.1.20151102124913
// ==/UserScript==
//Credits http://jacwright.com/projects/javascript/date_format
/*
Use this script to automatically log work in JIRA for tasks tracked xls or in slimtimer.

Slimtimer:
All you have to do is tag the tasks (and not time entries) in a specific pattern (JIRA@XXXX-NNN by default). With this done and after customizing variables mentioned below, you should be able to view links for each entry that can be logged on "Edit Entries" page of slimtimer website.
First link <b>XXXX-NNN</B> is issue link - just for convinience, opens the work log tab panel by default.
Second link <b>Auto Log</b> is to automatically log work in JIRA and tag the entry as Logged (customizable).
Third link <b>Manual Log</b> is to manually log work in JIRA (will keep the log work window open without logging any work so that you can edit the details if necessary)  and tag the entry as Logged (customizable).

The above links appear on the edit entries page for 'today'. If you navigate to any other date the link dissapear. To enable the links click <b>Enable JIRA</b> added to main menu of slimtimer (besides API menu).

You will have to customize following variables in the script for it to work.

1. jiraWebSiteLink - link to your JIRA website: You can extract it from any of the JIRA issue links.  e.g if your issue link is http://yourprogram.youwebsite.com/browse/XXX-NNNN, then this variabel should point to http://yourprogram.youwebsite.com.
<B font='red'>This must be customized else the JIRA links will be invalid</B>
2. loggedTag - Tag with which the time entry will be updated automatically if Auto Log or Manual Log links are clicked. 
Default - Logged

3. jiraPrefix - Prefix you would like to use in Slimtimer task tags.
Default - JIRA@


Issues:
1. All tags in time entry (not the task tags) are overwritten.
2. Work is logged at 10.00 AM in JIRA

Tested with JIRA Studio 2.2 and JIRA 3.13.3-#344

*/

new function() {

//variables to be customised for each site
var jiraWebSiteLink = "http://yourprogram.yourwebsite.com"
var loggedTag = 'Logged'
var jiraPrefix = 'JIRA@'

//my variables
//variables to be customised for each site
jiraWebSiteLink = "https://churchmutual.jira.com"
//jiraWebSiteLink = "http://spiderlogic.jira.com"
loggedTag = 'ttg-P.Planning and Tracking.Logged'
jiraPrefix = 'JIRA@'
jiraCommentPrefix = 'JC='

//dont change anything below unless you know what you are doing

var slimTimerWebsiteLinkRE = 'http*://*slimtimer.com/edit*'
var jiraIssueLinkRE = ".*/browse/.*"
var jiraWLLinkRE = ".*/secure/CreateWorklog!default.jspa.*"



var windowURL = window.location.href

var reURL = new RegExp(slimTimerWebsiteLinkRE)
var bExists = reURL.exec(windowURL);

if(bExists!=null){
	window.addEventListener("load", initializeST , false);
} else {
	reURL = new RegExp(jiraIssueLinkRE)
	bExists = reURL.exec(windowURL);
	
	if(bExists!=null){
		window.addEventListener("load", initializeJI , false);
	} else {
		reURL = new RegExp(jiraWLLinkRE)
		bExists = reURL.exec(windowURL);
		if(bExists!=null){
			window.addEventListener("load", initializeWL , false);
		}
	}
}

//slimtimer.com

function initializeST(){
	showMenu();
	addLinks();
}

function showMenu(){

	var menu = document.evaluate( "//*[@id='nav']/ul", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	menu=menu.snapshotItem(0);
	var div = document.createElement("li");
	var aLink = document.createElement("a");
	aLink.setAttribute("href","#")
	aLink.addEventListener('click', addLinks, true);
	aLink.innerHTML="Enable JIRA"
	div.appendChild(aLink);
	menu.appendChild(div)
	
	div = document.createElement("li");
	aLink = document.createElement("a");
	aLink.setAttribute("href","#")
	aLink.addEventListener('click', clickAllAutoLogLinks, true);
	aLink.innerHTML="Auto Log All"
	div.appendChild(aLink);
	menu.appendChild(div)
	
	
	var cal = document.evaluate( "//*[@id='edit-entries-header']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	cal=cal.snapshotItem(0);
	cal.addEventListener('load', addLinks, true);
}

function addLinks(){

	var dateToLog = document.evaluate( "//a[@id='current-date']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	dateToLog = dateToLog.snapshotItem(0).textContent;
	dateToLog = dateToLog.substring(3,dateToLog.length)
	//alert(dateToLog)

	var day = dateToLog.substring(0,dateToLog.indexOf("/"));
	dateToLog= dateToLog.substring(day.length+1, dateToLog.length);
	//alert(dateToLog)

	var month = dateToLog.substring(0 ,dateToLog.indexOf("/"));
	dateToLog= dateToLog.substring(month.length+1, dateToLog.length);
	//alert(dateToLog)

	var year = dateToLog.substring(0 ,dateToLog.length);
	//alert(year)


	dateToLog=new Date(year,month-1,day);
	//alert(dateToLog)

	dateToLog = dateToLog.format('d/M/Y')


	//alert(dateToLog)



	findPattern = "//div[@class='time-entry-content']/table/tbody"
	var entryTBody = document.evaluate( findPattern, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 

	for ( var i=0 ; i < entryTBody.snapshotLength; i++ )
	{
	  //alert( entryTBody.snapshotItem(i).textContent );
	  var tBody = entryTBody.snapshotItem(i)


	  var tags = document.evaluate( "tr/td[@class='column-1']/p[@class='tags']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
	  if (tags == null || tags.snapshotLength == 0 ) {
		continue;
	  }
	  tags=tags.snapshotItem(0);
	  //alert(tags.innerHTML)

	  var jiraLinkHref = null;
	  var jiraLinkTarget = null;
	  {
	  var re = new RegExp('\\b'+jiraPrefix+'[a-z,A-Z]*\\-[0-9]*')
	  var m = re.exec(tags.textContent);
	   if (m != null) {
	   //alert(m)
	     var s = "";
	     for (j = 0; j < m.length; j++) {
	       s = s + m[j] + "\n";
	     }
	     jiraLinkHref = s
	     jiraLinkTarget = s
	     jiraLinkHref = jiraLinkHref.replace(jiraPrefix, jiraWebSiteLink+'/browse/')
	     getTagLink(tBody,tags,jiraLinkHref,s, dateToLog)

	   }

	  }


	  {

	  var re = new RegExp('\\bhttp:.*\\d\\b')
	  var m = re.exec(tags.textContent);
	   if (m != null) {
	     var s = "";
	     for (j = 0; j < m.length; j++) {
	       s = s + m[j] + "\n";
	     }
	     jiraLinkHref = s
	     jiraLinkTarget = s
	     getTagLink(tBody,tags,jiraLinkHref,s, dateToLog)
	   }
	  }


	}

}


function getTagLink(tBody, tags, issueLink, issue, dateToLog){

	var hiddenElement = document.evaluate( "*//*[@id='slJIRAInt']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
	if(hiddenElement.snapshotLength>0){
		return;
	}
	
	hiddenElement = document.createElement("input");
	hiddenElement.type="hidden"
	hiddenElement.id = 'slJIRAInt'
	hiddenElement.setAttribute('issueLink',issueLink)
	hiddenElement.setAttribute('issueTagComment',createComment(tags))
	
	tags.appendChild(hiddenElement);
	
	var jiraIssueLink = createLink("#", issue.replace(jiraPrefix,""),"click", onIssueLinkClick )
	tags.appendChild(jiraIssueLink)

	var jiraAutoLogLink = createLink("#", "Auto Log","click", onLogLinkClick )
	tags.appendChild(jiraAutoLogLink)

	var jiraManualLogLink = createLink("#", "Manual Log","click", onLogLinkClick )
	tags.appendChild(jiraManualLogLink)
	
	
	setCommonParameters(hiddenElement, tBody, dateToLog)


}

function createComment(tags){
	var tagComment = "";
	var re = new RegExp('\\b'+jiraCommentPrefix+'[^,]*,')
	var m = re.exec(tags.textContent);
   	if (m != null) {
		//alert(m)
     		var s = "";
     		for (j = 0; j < m.length; j++) {
       			s = s + m[j] + "\n";
	     	}
    		tagComment = s
    		tagComment = tagComment.replace(jiraCommentPrefix,"");
    		tagComment = tagComment.replace(",","");
	}
	//alert(tagComment)
	return tagComment
}

function createLink(href, innerHTML, event, functionReference){
	var aLink = document.createElement("A");
	aLink.href=href	
	aLink.innerHTML = innerHTML
	aLink.addEventListener(event, functionReference, true)
	return aLink;
}

function gethiddenElement(aLink){
	return document.evaluate( "input[@id='slJIRAInt']", aLink.parentNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ).snapshotItem(0); 
}

function onIssueLinkClick(){
	var hiddenElement = gethiddenElement(this)
	var link  = hiddenElement.getAttribute("issueLink")+"?page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#issue-tabs"
	window.target= link
	window.open(link);	
}

function onLogLinkClick(){
	var hiddenElement = gethiddenElement(this)	
	window.open(hiddenElement.getAttribute('saveTagLink'))
	var link  = hiddenElement.getAttribute("link")
	alert(link+"&logWork="+this.innerHTML.replace(" Log",""));
	window.target= link
	window.open(link+"&logWork="+this.innerHTML.replace(" Log",""));

}


function setCommonParameters(hiddenElement, tBody, dateToLog){ 
	var duration = document.evaluate( "tr/td[@class='duration']", tBody,	null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	duration=duration.snapshotItem(0).textContent; 
	duration= duration.replace(":","h!").replace(new RegExp("[.].*","g"),"m").trim() 
	
	var comment = document.evaluate( "tr/td[@class='comments']/p", tBody,	null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	if(comment!=null && (comment=comment.snapshotItem(0))!=null){
		comment=comment.textContent; 
		//alert(comment)
	} else {
		comment=hiddenElement.getAttribute('issueTagComment');
		//alert(comment);
	}
	
	var timeRange = document.evaluate( "tr/td[@class='time-range']", tBody,	null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	timeRange=timeRange.snapshotItem(0).textContent; 
	
	var task = document.evaluate( "*//p[@class='task']", tBody,	null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); 
	task=task.snapshotItem(0).textContent; 
	
	var startTime = timeRange.substring(0,timeRange.indexOf('-')).trim()
	var endTime = timeRange.substring(timeRange.indexOf('-')+1, timeRange.length).trim()
	
	var jiraStartTime = startTime;
	var jiraStartTimeSuffix = "";
	if(jiraStartTime.indexOf('M')>=0){
		jiraStartTime = startTime.substring(0,startTime.length-2).trim();
		jiraStartTimeSuffix = startTime.substring(startTime.length-2,startTime.length).trim();
	} else {
		var jiraStartTimeHour = startTime.substring(0,startTime.indexOf(':')).trim();	
		var jiraStartTimeMin = startTime.substring(startTime.indexOf(':')+1,startTime.length).trim();
		if(jiraStartTimeHour != 12 && jiraStartTimeHour !=0){
			jiraStartTime = jiraStartTimeHour % 12 + ":" + jiraStartTimeMin
		} else {
			jiraStartTime = 12 + ":" + jiraStartTimeMin
		}
		//alert(jiraStartTimeHour/12%2)
		if(jiraStartTimeHour/12%2 < 1.0){ 
			jiraStartTimeSuffix = "AM";
		} else {
			jiraStartTimeSuffix = "PM";
		}
		
	}
	
	//alert(duration) 
	if(duration == "0m"){
		hiddenElement.setAttribute('link' , "javascript:alert('Nothing to log (0m cannot be logged)')");
	} else {
		hiddenElement.setAttribute ('link', hiddenElement.getAttribute("issueLink")+
			"?duration="+duration+"&dateToLog="+dateToLog+"!"+jiraStartTime+"!"+jiraStartTimeSuffix+"&workDescription="+comment); 
	} 

	
	var entryId = tBody.parentNode.parentNode.parentNode.id
	entryId = entryId.substring('time_entry-view-'.length, entryId.length-'-row'.length)
	
	
	var saveTagLink = '/edit/update/'+entryId +'?scaffold_id=time_entry'
	saveTagLink+='&duration-field-'+entryId +'='+duration
	saveTagLink+='&tag_input_'+entryId +'='+loggedTag
	saveTagLink+='&time_entry[tags]='+loggedTag
	saveTagLink+='&task_field_'+entryId +'='+task
	saveTagLink+='&time_entry[comments]='+comment
	saveTagLink+='&start-time-field-'+entryId +'='+startTime
	saveTagLink+='&end-time-field-'+entryId +'='+endTime
	//alert(saveTagLink)
	
	hiddenElement.setAttribute('saveTagLink',saveTagLink);

}

//Jira Issue


function initializeJI(){
	var logWork = gup('logWork')
	//alert(logWork)
	if(logWork == "Auto" || logWork == "Manual"){
		//alert(1)
		//try JIRa studio 2.2
		var logWorkLink = document.evaluate( "//a[@id='log-work'] | //a[@id='log_work']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
		logWorkLink=logWorkLink.snapshotItem(0);
		//alert(logWorkLink)
		logWorkLink = logWorkLink + (new RegExp("[?].*").exec(windowURL)+"").replace("?","&");
		//alert(logWorkLink)
		window.location.href=logWorkLink
	}
	

}



//JIra work log

function initializeWL(){
	var logWork = gup('logWork')
	//alert(logWork)
	if(logWork == "Auto" || logWork == "Manual"){
		var forSpaces = new RegExp("!","g");
		var duration = gup('duration').replace(forSpaces," ");
		
		var timeLogged = document.evaluate( "//input[@name='timeLogged']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
		var timeLogged = timeLogged.snapshotItem(0);
		timeLogged.value=duration;
		
		var dateToLog = gup('dateToLog').replace(forSpaces," ");;
		var startDate = document.evaluate( "//input[@name='startDate']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
		var startDate = startDate.snapshotItem(0);
		startDate.value=dateToLog;
				
		var workDescription = gup('workDescription').replace(forSpaces," ");;
		var comment = document.evaluate( "//textarea[@name='comment']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
		var comment = comment.snapshotItem(0);
		comment.innerHTML=workDescription;
		
		if(logWork == "Auto"){
			
			var logButton = document.evaluate( "//input[@name='Log']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
			var logButton = logButton.snapshotItem(0);
			//alert(logButton)
			logButton.click();
			//window.close();
			
		}
		
	}
	

}

function clickAllAutoLogLinks(){
	
	addLinks();
	
	var tags = document.evaluate( "//div[@class='time-entry-content']/table/tbody/tr/td[@class='column-1']/p[@class='tags']", 
			document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
			
	
	for ( var i=0 ; i < tags.snapshotLength; i++ ){
		var aTag = tags.snapshotItem(i)
		var aLink = document.evaluate("a[normalize-space(.)='Auto Log']", aTag, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
		if (aLink == null || aLink.snapshotLength==0){
			aTag.style.color="red";
			continue;
		}
		aLink = aLink.snapshotItem(0)
		if(aTag.textContent.indexOf(loggedTag)<0){
			//alert("clicking \n" + aTag.innerHTML + "\n " + loggedTag )
			aLink.click();
		} else {
	  		aLink.style.color="red";
	  		//alert("skipping \n" + aTag.innerHTML + "\n " + loggedTag )
	  	}
	  
	}
	alert("Refresh?")
	window.location.replace("http://slimtimer.com/edit");

}

//common***************************************************

function gup( name )
{
 //alert(name)
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( windowURL );
  if( results == null ){
    return "";
  }
  else{
    return unescape(results[1]);
  }
}


String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}


Date.prototype.format = function(format) {
	var returnStr = '';
	var replace = Date.replaceChars;
	for (var i = 0; i < format.length; i++) {
		var curChar = format.charAt(i);
		if (replace[curChar]) {
			returnStr += replace[curChar].call(this);
		} else {
			returnStr += curChar;
		}
	}
	return returnStr;
};
Date.replaceChars = {
	shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
	longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
	longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	
	// Day
	d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); },
	D: function() { return Date.replaceChars.shortDays[this.getDay()]; },
	j: function() { return this.getDate(); },
	l: function() { return Date.replaceChars.longDays[this.getDay()]; },
	N: function() { return this.getDay() + 1; },
	S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); },
	w: function() { return this.getDay(); },
	z: function() { return "Not Yet Supported"; },
	// Week
	W: function() { return "Not Yet Supported"; },
	// Month
	F: function() { return Date.replaceChars.longMonths[this.getMonth()]; },
	m: function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); },
	M: function() { return Date.replaceChars.shortMonths[this.getMonth()]; },
	n: function() { return this.getMonth() + 1; },
	t: function() { return "Not Yet Supported"; },
	// Year
	L: function() { return (((this.getFullYear()%4==0)&&(this.getFullYear()%100 != 0)) || (this.getFullYear()%400==0)) ? '1' : '0'; },
	o: function() { return "Not Supported"; },
	Y: function() { return this.getFullYear(); },
	y: function() { return ('' + this.getFullYear()).substr(2); },
	// Time
	a: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
	A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; },
	B: function() { return "Not Yet Supported"; },
	g: function() { return this.getHours() % 12 || 12; },
	G: function() { return this.getHours(); },
	h: function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); },
	H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); },
	i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); },
	s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); },
	// Timezone
	e: function() { return "Not Yet Supported"; },
	I: function() { return "Not Supported"; },
	O: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; },
	P: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + ':' + (Math.abs(this.getTimezoneOffset() % 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() % 60)); },
	T: function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result;},
	Z: function() { return -this.getTimezoneOffset() * 60; },
	// Full Date/Time
	c: function() { return this.format("Y-m-d") + "T" + this.format("H:i:sP"); },
	r: function() { return this.toString(); },
	U: function() { return this.getTime() / 1000; }
};

}