Skip to content

Instantly share code, notes, and snippets.

@ErgEnn
Last active January 27, 2023 11:29
Show Gist options
  • Save ErgEnn/6fd34add5aa1d6bfdd0f1b634b93a77e to your computer and use it in GitHub Desktop.
Save ErgEnn/6fd34add5aa1d6bfdd0f1b634b93a77e to your computer and use it in GitHub Desktop.
Shows total time of Jira ticket based on Toggl(client side only).
// ==UserScript==
// @name JiraTogglTotalTime
// @namespace https://gist.github.com/ErgEnn/6fd34add5aa1d6bfdd0f1b634b93a77e
// @version 0.3
// @description Shows total time of Jira ticket based on Toggl(client side only). Requires Toggl task descr to contain the ticket key.
// @author Ergo Enn
// @match https://*/browse*
// @match https://*/jira/software*
// @icon https://www.google.com/s2/favicons?sz=64&domain=atlassian.net
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @downloadURL https://gist.github.com/ErgEnn/6fd34add5aa1d6bfdd0f1b634b93a77e/raw/1067d304ce3c614ac829efc637de4c9d109a332b/jiraTogglTotalTime.user.js
// @updateURL https://gist.github.com/ErgEnn/6fd34add5aa1d6bfdd0f1b634b93a77e/raw/1067d304ce3c614ac829efc637de4c9d109a332b/jiraTogglTotalTime.user.js
// @grant GM.xmlHttpRequest
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
(async () => {
let togglToken = await GM.getValue("togglToken")
let togglWorkspaceId = await GM.getValue("togglWorkspaceId")
if(!togglToken){
GM.setValue("togglToken",prompt("API TOKEN REQUIRED. log in to track.toggl.com. From sidebar -> Profile -> Profile settings -> API Token"))
GM.setValue("togglWorkspaceId",prompt("WORKSPACE ID REQUIRED. log in to track.toggl.com. From sidebar -> click Team -> click your team name -> copy the id from url: ...workspaces/{id}/members"))
togglToken = await GM.getValue("togglToken")
togglWorkspaceId = await GM.getValue("togglWorkspaceId")
}
let upperDate = "2023-01-01" //TODO: Make request upper date limit dynamic
//TODO: Show notification when tracked time approaches or surpasses the estimation
var $j = jQuery.noConflict(true);
function toHMS(totalMilliSeconds){
var totalSeconds = totalMilliSeconds / 1000;
var hours = Math.floor(totalSeconds / 3600);
totalSeconds %= 3600;
var minutes = Math.floor(totalSeconds / 60);
var seconds = Math.floor(totalSeconds % 60);
return `${String(hours).padStart(2,'0')}h${String(minutes).padStart(2,'0')}m${String(seconds).padStart(2,'0')}s`;
}
var currentRefresher = 0;
function refreshTimeField(ticketNum){
var timeField = $j('*[data-testid="issue.views.issue-base.context.time-tracking.value"] small').children().first();
if(timeField.is(":visible")){
console.log("refreshing data for ticket "+ticketNum)
var historic = 0;
var current = 0;
GM.xmlHttpRequest({
method: "GET",
url: `https://api.track.toggl.com/reports/api/v2/summary?user_agent=ErgoEnnTogglJira&workspace_id=${togglWorkspaceId}&description=${ticketNum}&since=${upperDate}`,
headers: {
"Authorization": "Basic " + btoa(`${togglToken}:api_token`)
},
onload: function(response) {
var data = JSON.parse(response.responseText);
historic = data.data[0].time;
timeField.text(toHMS(historic+current))
}
});
GM.xmlHttpRequest({
method: "GET",
url: "https://api.track.toggl.com/api/v8/time_entries/current",
headers: {
"Authorization": "Basic " + btoa(`${togglToken}:api_token`)
},
onload: function(response) {
var data = JSON.parse(response.responseText);
var startTime = new Date(data.data.start).getTime();
if(currentRefresher)
window.clearInterval(currentRefresher);
if(data.data.description.startsWith(ticketNum)){
currentRefresher = window.setInterval(function(){
current = ((new Date()).getTime() - startTime)
timeField.text(toHMS(historic+current))
},100)
}else{
current = 0;
}
}
});
return true;
}
return false;
}
var refresher=0;
$j(document).ready(function () {
var prevTicketNum = 0;
var initialLoadDone = true;
$j( "body" ).bind("DOMSubtreeModified", function(){
var ticketNum = $j('*[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]').first().text();
if(ticketNum){
if(!initialLoadDone){
initialLoadDone = refreshTimeField(ticketNum);
}
if(prevTicketNum != ticketNum){
console.log("ticket changed from "+prevTicketNum+" to "+ticketNum)
prevTicketNum = ticketNum;
if (refresher)
window.clearInterval(refresher);
initialLoadDone = false;
refresher = window.setInterval(function(){refreshTimeField(ticketNum)},60000)
}
}else{
prevTicketNum = 0
if (refresher)
window.clearInterval(refresher);
}
});
});
})();
@ErgEnn
Copy link
Author

ErgEnn commented Oct 17, 2022

To install:

  1. Install Tampermonkey addon to chrome.
  2. Click RAW for this gist.
  3. Install
  4. Navigate to any Jira ticket(refresh if you already have it open)
  5. Popup appears to allow cross site requests - Allow all for domain.
  6. Open Tampermonkey dashboard from the plugin icon
  7. Open settings tab and change config mode to advanced
  8. Open installed userscripts tab and click edit on the right side of this script's row
  9. Select storage tab(if it does not exist you may need to refresh Jira ticket again or refresh the dashboard)
  10. Fill the settings in storage tab according to instructions
  11. Refresh Jira ticket again
    image

@EllenNetgroup
Copy link

EllenNetgroup commented Oct 17, 2022

Had isssue with:

  1. Popup appears to allow cross site requests - Allow all for domain.

To get popup you need to expand "More fields" tab under Jira ticket.
Otherwise LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment