NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Jira Tempo Worklog Extensions
// @description Extends issue detail to set tempo worklog billable times to zero (for one or all worklogs of an issue). Also shows the total worktime/billable time. Tested with Jira V7.12.3
// @author shindai
// @copyright 2019, shindai (https://openuserjs.org/users/shindai)
// @license MIT
// @include https://jira*/browse*
// @match https://jira*/browse*
// @updateURL https://openuserjs.org/meta/shindai/Jira_Tempo_Worklog_Extensions.meta.js
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @version 0.1.5
// @grant none
// ==/UserScript==
// PARAMETERS
var showTempoTimeStatistics = true;
//
// initiatilize my specific jQuery and avoid conflicts with Jira one
var $j = jQuery.noConflict(true);
var doing_modifications = false;
$j(document).ready(function()
{
AddStyling();
RegisterEvents();
var timer = 0;
$j("body").bind("DOMSubtreeModified", function()
{
if (timer)
{
window.clearTimeout(timer);
}
if (!doing_modifications) {
timer = window.setTimeout(function()
{
InitLayout();
}, 100);
}
});
});
// INIT METHODS
function AddStyling()
{
$j('head').append('<style type="text/css">' +
'.btn-billable-zero[data-billable-time="0"]{ opacity: 0.2 }' +
'</style>');
}
function RegisterEvents()
{
$j(document).on("click", ".btn-billable-zero-all", function()
{
if (confirm("Are you sure?"))
{
var issueId = window.jira.app.issue.getIssueId();
SetAllWorklogsForIssueToBillableZero(issueId, function(worklog)
{
$j(document).trigger("update-worklog-row", worklog);
}, function(parameter)
{
alert("Worklogs edit complete: " + parameter.successCounter + "/" + parameter.total + " were successfully edited!");
$j(document).trigger("update-worklog-stats");
});
}
});
$j(document).on("click", ".btn-billable-zero", function(element)
{
if (confirm("Are you sure?"))
{
var $element;
if($j(element.target).hasClass('.btn-billable-zero'))
{
$element = $j(element.target);
}
else
{
$element = $j(element.target).closest('.btn-billable-zero');
}
var issueId = $element.attr("data-issueid");
var worklogId = $element.attr("data-worklogid");
SetOneWorklogForIssueToBillableZero(
issueId,
worklogId,
function(worklog) {
alert("Worklog edit complete.");
$j(document).trigger("update-worklog-stats");
$j(document).trigger("update-worklog-row", worklog);
},
function() {
alert("Worklog edit failed.");
},
function() {}
);
}
});
$j(document).on('update-worklog-row', function(element, worklog)
{
var button = $j(".btn-billable-zero[data-worklogid='" + worklog.originId + "']");
if(button.length !== 0)
{
button.attr("data-billable-time", worklog.billableSeconds)
}
var worklogWorkLabel = $j("div[name='value_work_" + worklog.originId + "']");
if(worklogWorkLabel.length !== 0)
{
var billabelTimeLabel = worklogWorkLabel.parent().find('.billable-time-label');
if(billabelTimeLabel.length === 0)
{
billabelTimeLabel = $j("<div class='billable-time-label' title='billable time'></div>").insertAfter(worklogWorkLabel);
}
billabelTimeLabel.html("<span class=\"aui-icon aui-icon-small aui-iconfont-tag\" style=\"margin-top: 2px;\"></span> " + (worklog.billableSeconds/60/60) + "h");
}
});
$j(document).on('update-worklog-stats', function()
{
var $billableStatsContainer = $j(".billable-stats-container");
if($billableStatsContainer.length !== 0)
{
var issueId = window.jira.app.issue.getIssueId();
GetWorklogSumForIssue(issueId, function(result)
{
$billableStatsContainer.html("<span class=\"aui-icon aui-icon-small aui-iconfont-recent\" style=\"margin-top: 3px;\"></span> worked total: " + (result.totalTimeInSeconds/60/60) + "h | <span class=\"aui-icon aui-icon-small aui-iconfont-tag\" style=\"margin-top: 3px;\"></span> billable total: " + (result.totalTimeBillableInSeconds/60/60) + "h | <span title=\"billable percentage in comparison to time worked.\"><span class=\"aui-icon aui-icon-small aui-iconfont-graph-bar\" style=\"margin-top: 3px;\"></span> " + result.percentage + "% </span>");
}, function(worklog){
});
}
});
}
function InitLayout()
{
try
{
doing_modifications = true;
InitBillableInfoIfRequired();
InitAllBillableToZeroButtonIfRequired();
InitSingleBillableToZeroButtonsIfRequired();
} catch (exception) {
doing_modifications = false;
console.log("Error found on the page :" + exception);
}
doing_modifications = false;
}
function InitBillableInfoIfRequired()
{
if(showTempoTimeStatistics === false)
{
return;
}
var $tempoButtonRow = $j('#tempoIssueViewPanel .sc-esOvli');
if ($tempoButtonRow.length === 0)
{
return;
}
var $billableStatsContainer = $j('#tempoIssueViewPanel .sc-esOvli .billable-stats-container');
if ($billableStatsContainer.length !== 0)
{
return;
}
var issueId = window.jira.app.issue.getIssueId();
GetWorklogSumForIssue(issueId, function(result)
{
if($j('#tempoIssueViewPanel .sc-esOvli .billable-stats-container').length !== 0)
{
return;
}
$j("<div class=\"billable-stats-container ghx-label-11\" style=\"display:inline-block; padding: 6px;\"><span class=\"aui-icon aui-icon-small aui-iconfont-recent\" style=\"margin-top: 3px;\"></span> worked total: " + (result.totalTimeInSeconds/60/60) + "h | <span class=\"aui-icon aui-icon-small aui-iconfont-tag\" style=\"margin-top: 3px;\"></span> billable total: " + (result.totalTimeBillableInSeconds/60/60) + "h | <span title=\"billable percentage in comparison to time worked.\"><span class=\"aui-icon aui-icon-small aui-iconfont-graph-bar\" style=\"margin-top: 3px;\"></span> " + result.percentage + "% </span></div>").prependTo($tempoButtonRow);
}, function(workflow){
$j(document).trigger('update-worklog-row', workflow);
});
}
function InitAllBillableToZeroButtonIfRequired()
{
var $tempoButtonRow = $j('#tempoIssueViewPanel .sc-esOvli');
if ($tempoButtonRow.length === 0)
{
return;
}
var $billableZeroButton = $j('#tempoIssueViewPanel .sc-esOvli .btn-billable-zero-all');
if ($billableZeroButton.length !== 0)
{
return;
}
$j("<button class=\"tuiButton btn-billable-zero-all\" title=\"Set the billable time of all worklogs of this issue to 0.\"><span class=\"aui-icon aui-icon-small aui-iconfont-tag\" style=\"margin-top: 2px;\"></span>Set all worklogs to billable = 0</button>").appendTo($tempoButtonRow);
}
function InitSingleBillableToZeroButtonsIfRequired()
{
var $tempoWorklogsContainer = $j('#tempoIssueViewPanel #issuePanelTableWrapper');
if ($tempoWorklogsContainer.length === 0)
{
return;
}
var $rowActionContainers = $j('#tempoIssueViewPanel #issuePanelTableWrapper table tr .tempo-activity-actions-container');
if ($rowActionContainers.length === 0)
{
return;
}
var issueId = window.jira.app.issue.getIssueId();
for (var i = 0; i < $rowActionContainers.length; i++)
{
var $rowActionContainer = $j($rowActionContainers[i]);
if ($rowActionContainer.find(".btn-billable-zero").length > 0)
{
continue;
}
var selector = ".tuiDropdown__list button[name^=\"edit_work\"]";
var $editButton = $rowActionContainer.find(selector);
if ($editButton.length !== 1)
{
continue;
}
var btnName = $editButton.attr('name');
var worklogId = btnName.replace("edit_work_", "");
$j("<button class=\"tuiButton btn-billable-zero\" data-issueid=\"" + issueId + "\" data-worklogid=\"" + worklogId + "\" style=\"width: 54px;\" title=\"Set the billable time of this worklog to 0.\"><span class=\"aui-icon aui-icon-small aui-iconfont-tag\"></span><span class=\"aui-icon aui-icon-small aui-iconfont-new-arrow-down\"></span></button>").appendTo($rowActionContainer);
}
}
// API HELPER METHODS
function GetWorklogSumForIssue(issueId, sumResultCallback, singleWorklogResultCallback)
{
GetWorklogsForIssue(issueId, function(data)
{
function ResolveAll(worklogIdList)
{
var promises = [];
for (var i = 0; i < worklogIdList.length; i++) {
var worklogId = worklogIdList[i];
promises.push(new Promise(function(resolve, reject) {
GetWorklogById(worklogId, resolve, reject);
}));
}
return Promise.all(promises);
}
var worklogIds = data.activities.map(x => x.id);
var result = ResolveAll(worklogIds).then(function(results)
{
var totalTimeInSeconds = 0;
var totalTimeBillableInSeconds = 0;
for (var i = 0; i < results.length; i++)
{
var worklog = results[i];
totalTimeInSeconds += worklog.timeSpentSeconds;
totalTimeBillableInSeconds += worklog.billableSeconds;
singleWorklogResultCallback(worklog);
}
var percentage = Math.floor((totalTimeBillableInSeconds / totalTimeInSeconds) * 100, 2);
var result = {
percentage: percentage,
totalTimeInSeconds: totalTimeInSeconds,
totalTimeBillableInSeconds: totalTimeBillableInSeconds
};
sumResultCallback(result);
}, function(reason) {
console.log('fetching worklogs for statistics failed -> ' + reason);
});
});
}
function SetAllWorklogsForIssueToBillableZero(issueId, singleWorklogUpdateCallback, finishedCallback)
{
$j.get("/rest/tempo-time-activities/1/issue/" + issueId + "/?page=1&size=9999&activityType=work¤tUser=false", function(data)
{
var worklogIds = data.activities.map(x => x.id);
var counter = 0;
var successCounter = 0;
var failCounter = 0;
function showResult() {
if (counter === worklogIds.length)
{
var arg = { successCounter: successCounter, total: worklogIds.length }
finishedCallback(arg);
}
}
for (var i = 0; i < worklogIds.length; i++)
{
var worklogId = worklogIds[i];
SetOneWorklogForIssueToBillableZero(
issueId,
worklogId,
function() {
var worklog = arguments[1];
singleWorklogUpdateCallback(worklog);
successCounter++;
},
function() {
failCounter++;
},
function() {
counter++;
showResult();
}
);
}
});
}
function SetOneWorklogForIssueToBillableZero(issueId, worklogId, successCallback, failCallback, alwaysCallback)
{
var pObj = {
billableSeconds: 0,
originId: worklogId,
originTaskId: "" + issueId,
remainingEstimate: null,
endDate: null,
includeNonWorkingDays: false
};
$j.ajax({
type: "PUT",
url: "/rest/tempo-timesheets/4/worklogs/" + worklogId + "/",
contentType: "application/json",
data: JSON.stringify(pObj),
}).done(successCallback)
.fail(failCallback)
.always(alwaysCallback);
}
function GetWorklogsForIssue(issueId, successCallback)
{
$j.get("/rest/tempo-time-activities/1/issue/" + issueId + "/?page=1&size=9999&activityType=work¤tUser=false", successCallback);
}
function GetWorklogById(worklogId, resolve, reject)
{
$j.get("/rest/tempo-timesheets/4/worklogs/" + worklogId, resolve)
.fail(reject);
}