keithsalisbury-subtrac 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +56 -0
- data/VERSION.yml +4 -0
- data/bin/subtrac +40 -0
- data/lib/subtrac.rb +245 -0
- data/lib/subtrac/common/clients/index.wsgi +89 -0
- data/lib/subtrac/common/favicon.ico +0 -0
- data/lib/subtrac/common/images/trac/banner_bg.jpg +0 -0
- data/lib/subtrac/common/images/trac/bar_bg.gif +0 -0
- data/lib/subtrac/common/images/trac/footer_back.png +0 -0
- data/lib/subtrac/common/images/trac/main_bg.gif +0 -0
- data/lib/subtrac/common/images/trac/saint_logo_small.png +0 -0
- data/lib/subtrac/common/static/404.html +14 -0
- data/lib/subtrac/common/styles/trac.css +222 -0
- data/lib/subtrac/common/trac.ini +178 -0
- data/lib/subtrac/config/config.yml +54 -0
- data/lib/subtrac/passwords +1 -0
- data/lib/subtrac/shared/trac.ini +178 -0
- data/lib/subtrac/templates/location.erb +16 -0
- data/lib/subtrac/templates/projects/blank/svn/branches/README +0 -0
- data/lib/subtrac/templates/projects/blank/svn/tags/README +0 -0
- data/lib/subtrac/templates/projects/blank/svn/trunk/README +0 -0
- data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +57 -0
- data/lib/subtrac/templates/projects/new/svn/trunk/trac/wiki/WikiStart +46 -0
- data/lib/subtrac/templates/projects/new/trac/wiki/WikiStart +23 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/index/index.html +22 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/layout.html +56 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/site.html +27 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/theme.html +86 -0
- data/lib/subtrac/templates/projects/trac_theme/trac/wiki/WikiStart +4 -0
- data/lib/subtrac/templates/trac.erb +25 -0
- data/lib/subtrac/templates/vhost.erb +35 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +419 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +3 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +20 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +28 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +168 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +137 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +91 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +199 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +105 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +287 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +71 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +4 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +135 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +70 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +142 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +231 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +27 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +152 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +160 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +124 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +134 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +132 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +97 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +161 -0
- data/lib/subtrac/trac-plugins/clientsplugin/setup.py +43 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +4 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +273 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +44 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +36 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +32 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +409 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +30 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +53 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +181 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +66 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +47 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +93 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +86 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +20 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +23 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +128 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +40 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +285 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +173 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +164 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +69 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +1 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +292 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +172 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +178 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +25 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +6 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +356 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +100 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +84 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +1149 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +119 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +73 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +165 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +28 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +221 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +675 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +150 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +25 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +131 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +84 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +104 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +194 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +62 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +28 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +127 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +129 -0
- data/lib/subtrac/trac-plugins/worklogplugin/setup.py +29 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +1 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +187 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +67 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +12 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +7 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +208 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +1439 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.xcf +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/worklogplugin.css +80 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstart.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstop.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/manager.py +336 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +598 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +45 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +70 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +59 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +33 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +153 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +96 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +29 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +31 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +47 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +174 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +73 -0
- data/lib/subtrac/version.rb +4 -0
- metadata +191 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
|
2
|
+
//var linkify =
|
3
|
+
//(function(){
|
4
|
+
String.prototype.trim =
|
5
|
+
(function () {return this.replace(/^\s+/, "").replace(/\s+$/, "");});
|
6
|
+
var invalidDate = new Date("invalid").toString()
|
7
|
+
var billingfields= {}
|
8
|
+
var statusfields = []
|
9
|
+
function dateToUnixEpoch(date){
|
10
|
+
return Math.round(date.getTime()/1000) - (60 * date.getTimezoneOffset());
|
11
|
+
}
|
12
|
+
function addBillingField( name /*optional type defaults to "textbox", optional flag status*/ ){
|
13
|
+
var type = arguments.length >= 1 ? arguments[1] : "textbox";
|
14
|
+
var status = arguments.length >= 2 ? arguments[2] : false;
|
15
|
+
var getSet =
|
16
|
+
(function(){
|
17
|
+
var valueProp = "value";
|
18
|
+
|
19
|
+
if(type == "date"){
|
20
|
+
return function (/*optional value*/){
|
21
|
+
if(arguments.length == 0){
|
22
|
+
var d = new Date(this.$()[valueProp]);
|
23
|
+
if(d.toString == invalidDate){
|
24
|
+
alert("You entered an invalid "+name);
|
25
|
+
return null;
|
26
|
+
}
|
27
|
+
return dateToUnixEpoch(d);
|
28
|
+
}
|
29
|
+
else{
|
30
|
+
var val = new Date(arguments[0]);
|
31
|
+
if(val.toString == invalidDate){
|
32
|
+
this.$()[valueProp] = null;
|
33
|
+
return null;
|
34
|
+
}
|
35
|
+
this.$()[valueProp] = val;
|
36
|
+
return val;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
//FOR EVERYTHING ELSE
|
41
|
+
if(type == "checkbox"){
|
42
|
+
valueProp = "checked";
|
43
|
+
}
|
44
|
+
return function (/*optional value*/){
|
45
|
+
//alert(name+" : "+type+" "+valueProp);
|
46
|
+
if(arguments.length == 0){
|
47
|
+
var val = (this.$())[valueProp];
|
48
|
+
|
49
|
+
if(typeof(val) == "string") val = val.trim();
|
50
|
+
if(val)return val;
|
51
|
+
return null;
|
52
|
+
}
|
53
|
+
else{
|
54
|
+
var val = arguments[0];
|
55
|
+
(this.$())[valueProp] = val;
|
56
|
+
return val
|
57
|
+
}
|
58
|
+
}
|
59
|
+
})()
|
60
|
+
billingfields[name] = {
|
61
|
+
"$" : function(){
|
62
|
+
return document.getElementById(name);
|
63
|
+
},
|
64
|
+
getval : getSet,
|
65
|
+
setval : getSet
|
66
|
+
};
|
67
|
+
if (status){
|
68
|
+
statusfields.push({
|
69
|
+
name:name,
|
70
|
+
"$" : function(){
|
71
|
+
return document.getElementById(name);
|
72
|
+
},
|
73
|
+
getval : getSet,
|
74
|
+
setval : getSet
|
75
|
+
});
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
addBillingField("billable", "checkbox");
|
80
|
+
addBillingField("unbillable", "checkbox");
|
81
|
+
addBillingField("startdate", "date");
|
82
|
+
addBillingField("startbilling", "dateselect");
|
83
|
+
addBillingField("enddate", "date");
|
84
|
+
addBillingField("endbilling", "dateselect");
|
85
|
+
|
86
|
+
|
87
|
+
var linkify = function ( atag, basehref ){
|
88
|
+
var query = "";
|
89
|
+
var haveAdded = false;
|
90
|
+
function addToQuery(str){
|
91
|
+
query += haveAdded ? "&" : "?";
|
92
|
+
query += str;
|
93
|
+
haveAdded = true;
|
94
|
+
}
|
95
|
+
//billable logic
|
96
|
+
addToQuery(billingfields["billable"].getval() || !(billingfields["unbillable"].getval())
|
97
|
+
? "BILLABLE=1" : "BILLABLE=0");
|
98
|
+
addToQuery(billingfields["unbillable"].getval() || !(billingfields["billable"].getval())
|
99
|
+
? "UNBILLABLE=0" : "UNBILLABLE=1");
|
100
|
+
|
101
|
+
for(var i=0, f = null ; f = statusfields[i] ; i++){
|
102
|
+
val = f.name.toUpperCase().replace("_","", "g").replace(" ","","g")+"=";
|
103
|
+
if(f.getval()){
|
104
|
+
val += f.name
|
105
|
+
}
|
106
|
+
addToQuery(val);
|
107
|
+
}
|
108
|
+
|
109
|
+
//startdate the date in the text box or the date in the dropdown or the first time
|
110
|
+
startdate = billingfields["startdate"].getval() || billingfields["startbilling"].getval() || 0;
|
111
|
+
addToQuery("STARTDATE="+startdate);
|
112
|
+
//the date in the enddate text box or the date in the enddate billing box or real close to the end of integer unix epoch time
|
113
|
+
// this will need a patch to continue working past this point
|
114
|
+
enddate = billingfields["enddate"].getval() || billingfields["endbilling"].getval() || 2000000000;
|
115
|
+
addToQuery("ENDDATE="+enddate);
|
116
|
+
|
117
|
+
atag.href = basehref+query;
|
118
|
+
}
|
119
|
+
//})()
|
data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
(function(){
|
2
|
+
function AddEventListener( elem, evt, func, capture){
|
3
|
+
capture = capture || false;
|
4
|
+
if(elem.addEventListener) elem.addEventListener( evt, func, capture);
|
5
|
+
else elem.attachEvent('on'+evt, func);
|
6
|
+
return func;
|
7
|
+
};
|
8
|
+
var InitQuery = function(){
|
9
|
+
function createTableRow( numTds ){
|
10
|
+
var tr = document.createElement('tr');
|
11
|
+
var td = document.createElement('td');
|
12
|
+
td.style.backgroundColor="#EEF";
|
13
|
+
for(var i=0 ; i < numTds ; i++){
|
14
|
+
tr.appendChild(td.cloneNode(true));
|
15
|
+
}
|
16
|
+
return tr;
|
17
|
+
}
|
18
|
+
|
19
|
+
var _tbls = document.getElementsByTagName('table');
|
20
|
+
var tbls = [], tbl, cell;
|
21
|
+
// filter so that we only get ticket listing tables
|
22
|
+
for(var i=0 ; tbl = _tbls[i] ; i++ ){
|
23
|
+
if(tbl.className == 'listing tickets') tbls.push(tbl);
|
24
|
+
}
|
25
|
+
|
26
|
+
// find numerical columns
|
27
|
+
tbl = tbls[0];
|
28
|
+
|
29
|
+
var cells = tbl.tBodies[0].rows[0].cells;
|
30
|
+
var cellIdxs = [], columnNames = {};
|
31
|
+
for(var i=0 ; cell = cells[i] ; i++ ){
|
32
|
+
if(!isNaN(Number(cells[i].textContent))){
|
33
|
+
cellIdxs.push(i);
|
34
|
+
if(tbl && tbl.tHead.rows.length > 0)
|
35
|
+
columnNames[i] = tbl.tHead.rows[0].cells[i].textContent;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
// total the numerical columns and add a total row to each table
|
40
|
+
var totals, total_totals =[], idx;
|
41
|
+
for(var i=0 ; tbl = tbls[i] ; i++ ){
|
42
|
+
totals = [];
|
43
|
+
for(var k=0 ; row = tbl.tBodies[0].rows[k] ; k++){
|
44
|
+
for(var j=0 ; idx = cellIdxs[j] ; j++){
|
45
|
+
if(totals[idx] == null) totals[idx] = 0;
|
46
|
+
if(total_totals[idx] == null) total_totals[idx] = 0;
|
47
|
+
if(!isNaN(Number(row.cells[idx].textContent)))
|
48
|
+
var val = Number(row.cells[idx].textContent);
|
49
|
+
total_totals[idx] += val
|
50
|
+
totals[idx] += val;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
if(tbl.tBodies[0].rows.length > 0){
|
54
|
+
var tr = createTableRow(tbl.tBodies[0].rows[0].cells.length);
|
55
|
+
for(var j=0 ; idx = cellIdxs[j] ; j++){
|
56
|
+
tr.cells[idx].appendChild(document.createTextNode(totals[idx]));
|
57
|
+
}
|
58
|
+
tbl.tBodies[0].appendChild(tr);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
// If we are grouping by things, we are going to want to add a complete total to the bottom
|
62
|
+
if(tbls.length > 1){
|
63
|
+
var totalHtml = document.createElement('div');
|
64
|
+
totalHtml.style.backgroundColor="#EEF";
|
65
|
+
for(var j=0 ; idx = cellIdxs[j] ; j++){
|
66
|
+
totalHtml.appendChild(document.createTextNode("Total "+columnNames[idx]+": "+total_totals[idx]));
|
67
|
+
totalHtml.appendChild(document.createElement('br'));
|
68
|
+
}
|
69
|
+
tbls[0].parentNode.appendChild(totalHtml);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
AddEventListener(window, 'load', InitQuery)
|
73
|
+
})()
|
data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
(function(){
|
2
|
+
function teAddEventListener(elem, evt, func, capture)
|
3
|
+
{
|
4
|
+
capture = capture || false;
|
5
|
+
if (elem.addEventListener) elem.addEventListener(evt, func, capture);
|
6
|
+
else elem.attachEvent('on'+evt, func);
|
7
|
+
return func;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Function from: http://www.robertnyman.com/index.php?p=256
|
11
|
+
function getElementsByClassName(className, tag, elm)
|
12
|
+
{
|
13
|
+
var testClass = new RegExp("(^|\\s)" + className + "(\\s|$)");
|
14
|
+
var tag = tag || "*";
|
15
|
+
var elm = elm || document;
|
16
|
+
var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
|
17
|
+
var returnElements = [];
|
18
|
+
var current;
|
19
|
+
var length = elements.length;
|
20
|
+
for (var i=0; i<length; i++)
|
21
|
+
{
|
22
|
+
current = elements[i];
|
23
|
+
if(testClass.test(current.className))
|
24
|
+
{
|
25
|
+
returnElements.push(current);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
return returnElements;
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
function FloatToHoursMins(hours)
|
33
|
+
{
|
34
|
+
if (0 == hours) return hours;
|
35
|
+
var neg = false;
|
36
|
+
if(hours < 0){
|
37
|
+
neg = true;
|
38
|
+
hours *= -1;
|
39
|
+
}
|
40
|
+
mins = Math.floor((hours - Math.floor(hours)) * 60);
|
41
|
+
str = neg ? '-' : '';
|
42
|
+
if (hours) str += Math.floor(hours) + 'h';
|
43
|
+
if (mins) str += ' ' + mins + 'm';
|
44
|
+
return str;
|
45
|
+
}
|
46
|
+
|
47
|
+
function IntToYesNo(boolflag)
|
48
|
+
{
|
49
|
+
if (boolflag == '1')
|
50
|
+
return 'Yes';
|
51
|
+
|
52
|
+
if (boolflag == '0')
|
53
|
+
return 'No';
|
54
|
+
|
55
|
+
return boolflag;
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
InitBilling = function(){
|
60
|
+
/* // Convert totalhours field to non-editable
|
61
|
+
try
|
62
|
+
{
|
63
|
+
var x = document.getElementById('totalhours');
|
64
|
+
x = x || document.getElementById('field-totalhours');
|
65
|
+
if (x)
|
66
|
+
{
|
67
|
+
var p = x.parentNode;
|
68
|
+
var n = document.createElement('span')
|
69
|
+
n.id = x.id;
|
70
|
+
n.appendChild(document.createTextNode(x.value));
|
71
|
+
p.removeChild(x);
|
72
|
+
p.appendChild(n);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
catch (er) {}
|
76
|
+
*/
|
77
|
+
|
78
|
+
// Display yes/no in the summary
|
79
|
+
// if we fail, then no harm done.
|
80
|
+
try
|
81
|
+
{
|
82
|
+
var b = document.getElementById('h_billable');
|
83
|
+
do{ b = b.nextSibling; }while(b.nodeName != "TD");
|
84
|
+
b.innerHTML = IntToYesNo(b.innerHTML);
|
85
|
+
}
|
86
|
+
catch (er) {}
|
87
|
+
|
88
|
+
/*
|
89
|
+
// Hide the Add Hours in the title table
|
90
|
+
// if we fail, then no harm done.
|
91
|
+
try
|
92
|
+
{
|
93
|
+
var b = document.getElementById('h_hours');
|
94
|
+
b.innerHTML = '';
|
95
|
+
do{ b = b.nextSibling; }while(b.nodeName != "TD");
|
96
|
+
b.innerHTML = '';
|
97
|
+
}
|
98
|
+
catch (er) {}
|
99
|
+
*/
|
100
|
+
|
101
|
+
// Convert hours from float to hours minutes seconds
|
102
|
+
// if we fail, then no harm done.
|
103
|
+
try
|
104
|
+
{
|
105
|
+
fields = Array('estimatedhours', 'totalhours');
|
106
|
+
for (var i=0; i < 2; ++i)
|
107
|
+
{
|
108
|
+
var b = document.getElementById('h_' + fields[i]);
|
109
|
+
while (b)
|
110
|
+
{
|
111
|
+
if (!b.nextSibling) break;
|
112
|
+
b = b.nextSibling;
|
113
|
+
if (b.nodeName == 'TD')
|
114
|
+
{
|
115
|
+
b.innerHTML = FloatToHoursMins(b.innerHTML);
|
116
|
+
break;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
catch (er) {}
|
122
|
+
|
123
|
+
// Convert all relevent ticket changes to hours/minutes
|
124
|
+
// if we fail, then no harm done.
|
125
|
+
try {
|
126
|
+
changes = getElementsByClassName('changes', 'ul', document.getElementById('changelog'));
|
127
|
+
var change, li;
|
128
|
+
for (var i=0; change = changes[i]; i++) {
|
129
|
+
for (var j=0, li = change.childNodes[j]; li = change.childNodes[j]; j++) {
|
130
|
+
handleChangeRow(li);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
catch (er) {}
|
135
|
+
}
|
136
|
+
|
137
|
+
handleChangeRow = function(li){
|
138
|
+
var child, val, vals =[];
|
139
|
+
if (li.nodeName != 'LI') return;
|
140
|
+
// We look for a STRONG childNode
|
141
|
+
// We also need to find any em's following the STRONG
|
142
|
+
for(var i=0 ; child = li.childNodes[i] ; i++){
|
143
|
+
if (child.nodeName == 'STRONG'){
|
144
|
+
field = child.firstChild.nodeValue;
|
145
|
+
if(!(field == 'hours'
|
146
|
+
|| field == 'estimatedhours'
|
147
|
+
|| field == 'totalhours'))
|
148
|
+
return;
|
149
|
+
}
|
150
|
+
if (child.nodeName == 'EM'){
|
151
|
+
vals.push([child, child.firstChild.nodeValue])
|
152
|
+
}
|
153
|
+
}
|
154
|
+
for(var i=0; val = vals[i] ; i++){
|
155
|
+
out = FloatToHoursMins(Number(val[1]))
|
156
|
+
//print(val[0]+'|'+ val[1] )
|
157
|
+
//print("#"+ out)
|
158
|
+
val[0].innerHTML = out
|
159
|
+
}
|
160
|
+
return vals;
|
161
|
+
}
|
162
|
+
|
163
|
+
teAddEventListener(window, 'load', InitBilling)
|
164
|
+
})()
|
165
|
+
|
data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
import re
|
2
|
+
from trac.log import logger_factory
|
3
|
+
from trac.core import *
|
4
|
+
from trac.web import IRequestHandler
|
5
|
+
from trac.util import Markup
|
6
|
+
from trac.web.chrome import add_stylesheet, add_script, \
|
7
|
+
INavigationContributor, ITemplateProvider
|
8
|
+
from trac.web.href import Href
|
9
|
+
|
10
|
+
class QueryWebUiAddon(Component):
|
11
|
+
implements(INavigationContributor)
|
12
|
+
|
13
|
+
def __init__(self):
|
14
|
+
pass
|
15
|
+
|
16
|
+
# INavigationContributor methods
|
17
|
+
def get_active_navigation_item(self, req):
|
18
|
+
|
19
|
+
if re.search('query', req.path_info):
|
20
|
+
return "query-addon"
|
21
|
+
else:
|
22
|
+
return ""
|
23
|
+
|
24
|
+
def get_navigation_items(self, req):
|
25
|
+
src = req.href.chrome("Billing/query.js")
|
26
|
+
if re.search('query', req.path_info):
|
27
|
+
yield 'mainnav', "query-addon", \
|
28
|
+
Markup("""<script language="javascript" type="text/javascript" src="%s"></script>"""%src)
|
data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
from trac.core import *
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
class CustomReportManager:
|
6
|
+
"""A Class to manage custom reports"""
|
7
|
+
version = 1
|
8
|
+
name = "custom_report_manager_version"
|
9
|
+
env = None
|
10
|
+
log = None
|
11
|
+
TimingAndEstimationKey = "Timing and Estimation Plugin"
|
12
|
+
|
13
|
+
def __init__(self, env, log):
|
14
|
+
self.env = env
|
15
|
+
self.log = log
|
16
|
+
self.upgrade()
|
17
|
+
|
18
|
+
def upgrade(self):
|
19
|
+
# Check to see what version we have
|
20
|
+
db = self.env.get_db_cnx()
|
21
|
+
cursor = db.cursor()
|
22
|
+
cursor.execute("SELECT value FROM system WHERE name=%s", (self.name,))
|
23
|
+
try:
|
24
|
+
version = int(cursor.fetchone()[0])
|
25
|
+
except:
|
26
|
+
version = 0
|
27
|
+
cursor.execute("INSERT INTO system (name,value) VALUES(%s,%s)",
|
28
|
+
(self.name, version))
|
29
|
+
|
30
|
+
if version > self.version:
|
31
|
+
raise TracError("Fatal Error: You appear to be running two plugins with conflicting versions "
|
32
|
+
"of the CustomReportManager class. Please ensure that '%s' is updated to "
|
33
|
+
"version %s of the file reportmanager.py (currently using version %s)."
|
34
|
+
% (__name__, str(version), str(self.version)))
|
35
|
+
|
36
|
+
# Do the staged updates
|
37
|
+
try:
|
38
|
+
if version < 1:
|
39
|
+
cursor.execute("CREATE TABLE custom_report ("
|
40
|
+
"id INTEGER,"
|
41
|
+
"uuid VARCHAR(64),"
|
42
|
+
"maingroup VARCHAR(255),"
|
43
|
+
"subgroup VARCHAR(255),"
|
44
|
+
"version INTEGER,"
|
45
|
+
"ordering INTEGER)")
|
46
|
+
|
47
|
+
#if version < 2:
|
48
|
+
# cursor.execute("...")
|
49
|
+
|
50
|
+
# Updates complete, set the version
|
51
|
+
cursor.execute("UPDATE system SET value=%s WHERE name=%s",
|
52
|
+
(self.version, self.name))
|
53
|
+
db.commit()
|
54
|
+
db.close()
|
55
|
+
|
56
|
+
except Exception, e:
|
57
|
+
self.log.error("CustomReportManager Exception: %s" % (e,));
|
58
|
+
db.rollback()
|
59
|
+
|
60
|
+
def get_report_id_and_version (self, uuid):
|
61
|
+
sql = "SELECT custom_report.id, custom_report.version FROM custom_report "\
|
62
|
+
"JOIN report ON report.id = custom_report.id " \
|
63
|
+
"WHERE uuid=%s"
|
64
|
+
tpl = self.get_first_row(sql, uuid)
|
65
|
+
return tpl or (None, 0)
|
66
|
+
|
67
|
+
def get_new_report_id (self):
|
68
|
+
"""find the next available report id """
|
69
|
+
rtn = self.get_scalar("SELECT MAX(id) FROM report")
|
70
|
+
return (rtn and rtn+1) or 1
|
71
|
+
|
72
|
+
def get_max_ordering(self, maingroup, subgroup):
|
73
|
+
""" Find the maximum ordering value used for this group of the custom_report table"""
|
74
|
+
return self.get_scalar("SELECT MAX(ordering) FROM custom_report WHERE maingroup=%s AND subgroup=%s",
|
75
|
+
0, maingroup, subgroup) or 0
|
76
|
+
|
77
|
+
def _insert_report (self, next_id, title, author, description, query,
|
78
|
+
uuid, maingroup, subgroup, version, ordering):
|
79
|
+
""" Adds a row the custom_report_table """
|
80
|
+
self.log.debug("Inserting new report '%s' with uuid '%s'" % (title,uuid))
|
81
|
+
self.execute_in_trans(("DELETE FROM custom_report WHERE uuid=%s", (uuid,)),
|
82
|
+
("INSERT INTO report (id, title, author, description, query) " \
|
83
|
+
"VALUES (%s, %s, %s, %s, %s)",
|
84
|
+
(next_id, title, author, description, query)),
|
85
|
+
("INSERT INTO custom_report (id, uuid, maingroup, subgroup, version, ordering) " \
|
86
|
+
"VALUES (%s, %s, %s, %s, %s, %s)",
|
87
|
+
(next_id, uuid, maingroup, subgroup, version, ordering)))
|
88
|
+
self.log.debug("Attempting to increment sequence (only works in postgres)")
|
89
|
+
try:
|
90
|
+
self.execute_in_trans(("SELECT nextval('report_id_seq');",[]));
|
91
|
+
self.log.debug("Sequence updated");
|
92
|
+
except:
|
93
|
+
self.log.debug("Sequence failed to update, perhaps you are not running postgres?");
|
94
|
+
|
95
|
+
def _update_report (self, id, title, author, description, query,
|
96
|
+
maingroup, subgroup, version):
|
97
|
+
"""Updates a report and its row in the custom_report table """
|
98
|
+
self.log.debug("Updating report '%s' with to version %s" % (title, version))
|
99
|
+
self.execute_in_trans(("UPDATE report SET title=%s, author=%s, description=%s, query=%s " \
|
100
|
+
"WHERE id=%s", (title, author, description, query, id)),
|
101
|
+
("UPDATE custom_report SET version=%s, maingroup=%s, subgroup=%s "
|
102
|
+
"WHERE id=%s", (version, maingroup, subgroup, id)))
|
103
|
+
|
104
|
+
def add_report(self, title, author, description, query, uuid, version,
|
105
|
+
maingroup, subgroup="", force=False):
|
106
|
+
"""
|
107
|
+
We add/update a report to the system. We will not overwrite unchanged versions
|
108
|
+
unless force is set.
|
109
|
+
"""
|
110
|
+
# First check to see if we can load an existing version of this report
|
111
|
+
(id, currentversion) = self.get_report_id_and_version(uuid)
|
112
|
+
self.log.debug("add_report %s (ver:%s) | id: %s currentversion: %s" % (uuid , version, id, currentversion))
|
113
|
+
try:
|
114
|
+
if not id:
|
115
|
+
next_id = self.get_new_report_id()
|
116
|
+
ordering = self.get_max_ordering(maingroup, subgroup) + 1
|
117
|
+
self._insert_report(next_id, title, author, description, query,
|
118
|
+
uuid, maingroup, subgroup, version, ordering)
|
119
|
+
return True
|
120
|
+
if currentversion < version or force:
|
121
|
+
self._update_report(id, title, author, description, query,
|
122
|
+
maingroup, subgroup, version)
|
123
|
+
return True
|
124
|
+
except Exception, e:
|
125
|
+
self.log.error("CustomReportManager.add_report Exception: %s, %s" % (e,(title, author, uuid, version,
|
126
|
+
maingroup, subgroup, force)));
|
127
|
+
self.log.debug("report %s not upgraded (a better version already exists)" % uuid)
|
128
|
+
return False
|
129
|
+
|
130
|
+
def get_report_by_uuid(self, uuid):
|
131
|
+
sql = "SELECT report.id,report.title FROM custom_report "\
|
132
|
+
"LEFT JOIN report ON custom_report.id=report.id "\
|
133
|
+
"WHERE custom_report.uuid=%s"
|
134
|
+
return self.get_first_row(sql,uuid)
|
135
|
+
|
136
|
+
def get_reports_by_group(self, group):
|
137
|
+
"""Gets all of the reports for a given group"""
|
138
|
+
db = self.env.get_db_cnx()
|
139
|
+
cursor = db.cursor()
|
140
|
+
rv = {}
|
141
|
+
try:
|
142
|
+
cursor.execute("SELECT custom_report.subgroup,report.id,report.title, custom_report.version, custom_report.uuid "
|
143
|
+
"FROM custom_report "
|
144
|
+
"LEFT JOIN report ON custom_report.id=report.id "
|
145
|
+
"WHERE custom_report.maingroup=%s "
|
146
|
+
"ORDER BY custom_report.subgroup,custom_report.ordering", (group,))
|
147
|
+
for subgroup, id, title, version, uuid in cursor:
|
148
|
+
if not rv.has_key(subgroup):
|
149
|
+
rv[subgroup] = { "title": subgroup,
|
150
|
+
"reports": [] }
|
151
|
+
rv[subgroup]["reports"].append( { "id": int(id), "title": title, "version":version, "uuid":uuid } )
|
152
|
+
except:
|
153
|
+
pass
|
154
|
+
return rv
|
155
|
+
|
156
|
+
def get_version_hash_by_group(self, group):
|
157
|
+
"""Gets all of the reports for a given group as a uuid=>version hash"""
|
158
|
+
db = self.env.get_db_cnx()
|
159
|
+
cursor = db.cursor()
|
160
|
+
rv = {}
|
161
|
+
try:
|
162
|
+
cursor.execute("SELECT custom_report.subgroup,report.id,report.title, custom_report.version, custom_report.uuid "
|
163
|
+
"FROM custom_report "
|
164
|
+
"LEFT JOIN report ON custom_report.id=report.id "
|
165
|
+
"WHERE custom_report.maingroup=%s "
|
166
|
+
"ORDER BY custom_report.subgroup,custom_report.ordering", (group,))
|
167
|
+
for subgroup, id, title, version, uuid in cursor:
|
168
|
+
rv[uuid] = version
|
169
|
+
except:
|
170
|
+
pass
|
171
|
+
return rv
|
172
|
+
|
173
|
+
# similar functions are found in dbhelper, but this file should be fairly
|
174
|
+
# stand alone so that it can be copied and pasted around
|
175
|
+
def get_first_row(self, sql,*params):
|
176
|
+
""" Returns the first row of the query results as a tuple of values (or None)"""
|
177
|
+
db = self.env.get_db_cnx()
|
178
|
+
cur = db.cursor()
|
179
|
+
data = None;
|
180
|
+
try:
|
181
|
+
cur.execute(sql, params)
|
182
|
+
data = cur.fetchone();
|
183
|
+
db.commit();
|
184
|
+
except Exception, e:
|
185
|
+
self.log.error('There was a problem executing sql:%s \n \
|
186
|
+
with parameters:%s\nException:%s'%(sql, params, e));
|
187
|
+
db.rollback()
|
188
|
+
try:
|
189
|
+
db.close()
|
190
|
+
except:
|
191
|
+
pass
|
192
|
+
return data;
|
193
|
+
|
194
|
+
def get_scalar(self, sql, col=0, *params):
|
195
|
+
""" Gets a single value (in the specified column) from the result set of the query"""
|
196
|
+
data = self.get_first_row(sql, *params);
|
197
|
+
if data:
|
198
|
+
return data[col]
|
199
|
+
else:
|
200
|
+
return None;
|
201
|
+
|
202
|
+
def execute_in_trans(self, *args):
|
203
|
+
success = True
|
204
|
+
db = self.env.get_db_cnx()
|
205
|
+
cur = db.cursor()
|
206
|
+
try:
|
207
|
+
for sql, params in args:
|
208
|
+
cur.execute(sql, params)
|
209
|
+
db.commit()
|
210
|
+
except Exception, e:
|
211
|
+
self.log.error('There was a problem executing sql:%s \n \
|
212
|
+
with parameters:%s\nException:%s'%(sql, params, e));
|
213
|
+
db.rollback();
|
214
|
+
success = False
|
215
|
+
try:
|
216
|
+
db.close()
|
217
|
+
except:
|
218
|
+
pass
|
219
|
+
return success
|
220
|
+
|
221
|
+
|