Last active
February 12, 2018 02:19
-
-
Save callumgare/000aa9285b856a15be5a039c973a365b to your computer and use it in GitHub Desktop.
Adds a timer to a Targetprocess user story so you can clock on, clock off, and have that saved as a time to that user story
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************************************ | |
* Timer Mashup for TargetProcess | |
* Author: Callum Gare from Melbourne Business Systems | |
* Contact: [email protected] | |
* Requires: The custom fields StartTime and EndTime for the TIME enity | |
* | |
* Inserts a Start/Stop Timer button below the title of a UserStory card | |
* https://i.imgur.com/9PhIeh1.png | |
************************************/ | |
var apiRoot; | |
var user; | |
tau.mashups | |
.addDependency('jQuery') | |
.addDependency('app.path') | |
.addDependency('tp3/mashups/context') | |
.addMashup(function($, appPath, context, config) { | |
context.getLoggedUser().then(function(u) {user = u}); | |
apiRoot = appPath.get() + '/api/v1'; | |
init(); | |
}); | |
function timerElm_toggle(elm) { | |
if(!elm.dataset.runningTimer){ | |
$(elm).css('opacity', 0.5); | |
startTimer('Running Timer', elm.dataset.userstoryId, function(err,res,status) { | |
if(err){ | |
alert('Timer could not be started. Are the Target Process Timer extention settings correct?'); | |
return; | |
} | |
$(elm).text('Stop Timer'); | |
$(elm).css('opacity', ''); | |
elm.dataset.runningTimer = [res.Id]; | |
}); | |
} else { | |
$(elm).css('opacity', 0.5); | |
stopAllTimers(parseInt(elm.dataset.userstoryId), function(err,res,status) { | |
if(err){ | |
alert('Timer could not be stopped. Are the Target Process Timer extention settings correct?'); | |
return; | |
} | |
$(elm).text('Start Timer'); | |
$(elm).css('opacity', ''); | |
elm.dataset.runningTimer = ''; | |
}); | |
} | |
} | |
function startTimer(taskTitle, userstoryId, cb, fail_cb){ | |
$.ajax({ | |
type: 'POST', | |
url: apiRoot+'/Times?format=json', | |
contentType: 'application/json; charset=UTF-8', | |
processData: false, | |
data: JSON.stringify({ | |
Spent:0, | |
Remain:0, | |
Date: (new Date()).toISOString(), | |
Description: taskTitle, | |
User: {Id: user.id}, | |
Assignable: {Id: userstoryId}, | |
CustomFields: [ | |
{ | |
Name: "StartTime", | |
Value: Date.now() | |
} | |
] | |
}), | |
}) | |
.done( function(data) {cb(null,data)} ) | |
.fail( function(xhr, status, errorThrown) {cb({xhr: xhr, status: status, errorThrown: errorThrown})} ); | |
} | |
function stopTimer(time, cb){ | |
var startTime = time.CustomFields.filter(function(field){ | |
return field.Name === 'StartTime' && typeof field.Value == 'number'; | |
}); | |
if( startTime.length > 0 ){ | |
startTime = parseInt(startTime[0].Value); | |
} else { | |
return cb("No start time, so can't stop", null) | |
} | |
var duration = (Date.now() - startTime)/1000/60/60; | |
var description = time.Description === 'Running Timer' ? '' : time.Description; | |
updateTime({ | |
Spent: Math.ceil(duration*4)/4, | |
Description: description+' - '+(duration*60).toFixed(2)+ ' minutes', | |
Remain: 0, | |
Id: time.Id, | |
CustomFields: [ | |
{ | |
Name: "EndTime", | |
Value: Date.now() | |
} | |
] | |
}, cb); | |
} | |
function stopAllTimers(id, cb){ | |
getRunningTimers(id, function(err, times) { | |
//debugger; | |
var remaningTimers = times.length; | |
times.forEach(function(time) { | |
stopTimer(time, function(err){ | |
if(--remaningTimers < 1) cb(); | |
console.log(remaningTimers); | |
}) | |
}); | |
}); | |
} | |
function getRunningTimers(userstoryId, cb){ | |
$.ajax({ | |
type: 'GET', | |
url: apiRoot+'/Times', | |
contentType: 'application/json; charset=UTF-8', | |
data: { | |
where: '(UserStory.Id eq '+userstoryId+') and (CustomFields.StartTime is not null) '+ | |
'and (CustomFields.EndTime is null)', | |
format: 'json' | |
}, | |
}) | |
.done( function(data) {cb(null,data.Items)} ) | |
.fail( function(xhr, status, errorThrown) {cb({xhr: xhr, status: status, errorThrown: errorThrown})} ); | |
} | |
function updateTime(time, cb){ | |
$.ajax({ | |
type: 'POST', | |
url: apiRoot+'/Times?format=json', | |
contentType: 'application/json; charset=UTF-8', | |
processData: false, | |
data: JSON.stringify(time), | |
}) | |
.done( function(data) {cb(null,data)} ) | |
.fail( function(xhr, status, errorThrown) {cb({xhr: xhr, status: status, errorThrown: errorThrown})} ); | |
} | |
function init(){ | |
// Nabbed from https://davidwalsh.name/mutationobserver-api | |
var observerConfig = { | |
attributes: true, | |
childList: true, | |
characterData: true | |
}; | |
var observer = new MutationObserver(function(mutations) { | |
mutations.forEach(function(mutation) { | |
// if there's an open user story card that hasn't had a timer added to it yet, add it | |
$('div.general-info:not(.trackingTimer)').each(function(i, elm) { | |
if($(elm).hasClass('trackingTimer')) return; | |
$(elm).addClass('trackingTimer'); | |
var id = parseInt($('div.general-info').find('.entity-id').first().text().match(/#(\d*)/)[1]); | |
getRunningTimers(id, function(err,times) { | |
var runningTimers = times && times.constructor === Array && times.length > 0 ? times.map(function(time){return time.Id}) : undefined; | |
var timer; | |
if( runningTimers ) { | |
timer = $('<div>Stop Timer</div>'); | |
timer.get(0).dataset.runningTimer = runningTimers; | |
} else { | |
timer = $('<div>Start Timer</div>'); | |
} | |
timer.get(0).dataset.userstoryId = id; | |
timer.click(function(){timerElm_toggle(timer.get(0))}); | |
$(elm).find('.i-role-title').parent().append(timer); | |
}); | |
}); | |
}); | |
}); | |
var targetNode = document.body; | |
observer.observe(targetNode, observerConfig); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment