Last active
December 15, 2015 17:50
-
-
Save angry-dan/5299401 to your computer and use it in GitHub Desktop.
Sum of hours in a Redmine task list
This file contains 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
// ==UserScript== | |
// @name Redmine Hour Counter | |
// @namespace http://projects.deeson.info | |
// @description Adds up selected hours. | |
// @include https://projects.deeson.info/* | |
// @grant none | |
// ==/UserScript== | |
/** | |
* Adds the count icon at the top of the page. | |
* When clicked it counts the total number of hours. | |
*/ | |
loadJQuery(function ($) { | |
function update() { | |
var selector = 'tr:not(.parent) .estimated_hours'; | |
if ($('tr.context-menu-selection').length) { | |
selector = 'tr.context-menu-selection:not(.parent) .estimated_hours'; | |
} | |
var incomplete; | |
var hours = $(selector) | |
.map(function() { | |
return parseFloat($(this).text()) | |
}) | |
.toArray() | |
.reduce(function(initial, current) { | |
if (isNaN(current)) { | |
incomplete = true; | |
return initial; | |
} | |
return initial + current; | |
}, 0); | |
var message = hours + (hours == 1 ? ' hour' : ' hours'); | |
if (incomplete) { | |
message += " <small>(incomplete)</small>"; | |
} | |
if (!hours) { | |
message = '<small>No estimate</small>'; | |
} | |
$button.html(message); | |
} | |
var enableUx = $(document).has('.subject').length; | |
// enableUx = false; | |
if ($(document).has('.estimated_hours').length) { | |
var $button = $('<span class="icon icon-time" style="float: right;"></span>'); | |
$('#query_form p.buttons').append($button); | |
$(update); | |
$(document).click(update); | |
if (enableUx) { | |
// Array of header cells to hide. | |
var headersToHide = []; | |
$('.subject') | |
.each(function(){ | |
var subject = $(this), | |
inner = $('a',subject); | |
// Estimates | |
var estimate = subject.siblings('.estimated_hours'); | |
estimate.hide(); | |
if (!headersToHide[0]) { | |
headersToHide.push(estimate.index()); | |
} | |
if (estimate.html()) { | |
inner.prepend('<span class="inline_estimated_hours" style="font-size: smaller;">[' + estimate.html() + ']</span> '); | |
} | |
// Assignees | |
// Due dates | |
// Tracker, Status, all go as icons. Priority, Severity might be combined into one somehow? | |
// Category, Updated, Target version goes | |
}); | |
var headers = $('.issues th'); | |
$.each(headersToHide, function(k,i) { | |
headers.eq(i).hide(); | |
}); | |
} | |
} | |
trackerIcons(); | |
/** | |
* Uses jQuery to convert the tracker names to icons. | |
*/ | |
function trackerIcons() { | |
$('table.issues td.tracker') | |
.each(function(){ | |
var originalText = $(this).text(); | |
var css = originalText.toLowerCase().replace(/[^a-z0-9]+/g,'-'); | |
var replacement = $('<span />').addClass(css).addClass('tracker-icon').text(originalText).attr('title', originalText); | |
$(this).empty().append(replacement); | |
}); | |
addGlobalStyle(getTrackerIconCss()); | |
} | |
/** | |
* Returns CSS for presenting the tracker icons. | |
*/ | |
function getTrackerIconCss() { | |
var css = '.tracker-icon{background-position: 0 0;display: inline-block;height: 16px;text-indent: -999em;width: 16px;background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAAgCAMAAADt/IAXAAADAFBMVEUOXI3u7u4OXI0OXI3u7u4OXI4QY5f////9/f3///8QY5cPX5H///8QY5f19fXy8vIPXY////8QY5f///////8QY5cQY5cQY5f///8QY5f///8PYJP///8QY5f///8QY5cQY5f///8PXo/5+fkPYJP///8QY5f////+/v4PXY0QY5cPYpYQY5fz8/MPXpD29vb////8/Pz///8QY5f19fXu7u4PXY309PQOXI3///////8QY5f///////8QY5f39/f///8QY5f///8QY5cQY5f////x8fH////x8fEPXY8QY5f///////8QY5f///////8PX5L////z8/P///8PXpDw8PAQY5fu7u709PQPYpX///8PXY7w8PDw8PDv7+8PXI3///////8QY5f///////////////8QY5f///8QY5f///8PYZTz8/MQY5fz8/P6+vr8/Pz///8QY5cQY5cPYZUQY5f///////8QY5f///////8QY5f///////8QY5f///8QY5cQY5f///////8QY5f///////8PYJLv7+/////5+fkPYJMQY5f////29vYPX5EQY5cQY5f///8QY5f////////////u7u4QY5f///8PYZQQY5cQY5f////09PT7+/sQY5cQY5f///8QY5cQY5f///////8PXY4QY5f///////8QY5f///8QY5cQY5cQY5f9/f0PX5H///8QY5f///////8QY5f///////8PYpYPXpAQY5cQY5cQY5f////w8PAQY5f4+Pjx8fEQY5f///////////8QY5cQY5cQY5cQY5f///8PXY719fUQY5f///8QY5cQY5f///8QY5fy8vL///8PXpAPXo8QY5f///8PX5EQY5f///8QY5cQY5fx8fEQY5cPXY8PXY7///8PYZT6+voQY5cQY5f///////8QY5f///8QY5cAAAAOXpAOXpAOXpHv7+8OXpAPXY7u7u77+/v9/f3x8fH6+vr8/Pz39/cPXY/+/v4PYZX///8QY5dLTLOzAAAA7nRSTlP+/vz//v5qZ6eEbch2dsvg4JmYnTGZRZ14RIe8TngvTop82rW1Xnw9o/IZo17U1MVyqDZyzfnxzvkOV1eAVlbCi4BJL41L5Czh4SxgenoE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);}'; | |
css += '.tracker-icon.bug {background-position: 0px 0;}'; | |
css += '.tracker-icon.feature {background-position: -16px 0;}'; | |
css += '.tracker-icon.epic-feature {background-position: -32px 0;}'; | |
css += '.tracker-icon.support {background-position: -48px 0;}'; | |
css += '.tracker-icon.content {background-position: -64px 0;}'; | |
css += '.tracker-icon.checklist {background-position: -80px 0;}'; | |
css += '.tracker-icon.checklist-summary {background-position: -96px 0;}'; | |
css += '.tracker-icon.code-quality {background-position: -112px 0;}'; | |
css += '.tracker-icon.change-request {background-position: -128px 0;}'; | |
css += '.context-menu-selection .tracker-icon.bug {background-position: 0px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.feature {background-position: -16px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.epic-feature {background-position: -32px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.support {background-position: -48px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.content {background-position: -64px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.checklist {background-position: -80px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.checklist-summary {background-position: -96px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.code-quality {background-position: -112px 16px;}'; | |
css += '.context-menu-selection .tracker-icon.change-request {background-position: -128px 16px;}'; | |
return css; | |
} | |
/** | |
* Inject CSS into the document head. | |
*/ | |
function addGlobalStyle(css) { | |
var head, style; | |
head = document.getElementsByTagName('head')[0]; | |
if (!head) { return; } | |
style = document.createElement('style'); | |
style.type = 'text/css'; | |
style.innerHTML = css; | |
head.appendChild(style); | |
} | |
}); | |
/** | |
* Adapted from http://erikvold.com/blog/index.cfm/2010/6/14/using-jquery-with-a-user-script. | |
* Injects a JQuery script element into the page, then injects our script into the dom | |
* for execution at local scope (i.e. with access to JQuery) | |
* | |
* Remember that the contents of callback are evaluated at the window scope, so | |
* it won't have access to anything else defined in this file. | |
*/ | |
function loadJQuery(callback) { | |
var script = document.createElement("script"); | |
script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"); | |
script.addEventListener('load', function() { | |
var script = document.createElement("script"); | |
script.textContent = "(" + callback.toString() + ")(jQuery.noConflict(true));"; | |
document.body.appendChild(script); | |
}, false); | |
document.body.appendChild(script); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment