Skip to content

Instantly share code, notes, and snippets.

@callumgare
Last active February 12, 2018 02:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save callumgare/000aa9285b856a15be5a039c973a365b to your computer and use it in GitHub Desktop.
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
/************************************
* Timer Mashup for TargetProcess
* Author: Callum Gare from Melbourne Business Systems
* Contact: callum@mbsystems.com.au
* 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