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); }