keithsalisbury-subtrac 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +7 -0
  3. data/Rakefile +56 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/subtrac +40 -0
  6. data/lib/subtrac.rb +245 -0
  7. data/lib/subtrac/common/clients/index.wsgi +89 -0
  8. data/lib/subtrac/common/favicon.ico +0 -0
  9. data/lib/subtrac/common/images/trac/banner_bg.jpg +0 -0
  10. data/lib/subtrac/common/images/trac/bar_bg.gif +0 -0
  11. data/lib/subtrac/common/images/trac/footer_back.png +0 -0
  12. data/lib/subtrac/common/images/trac/main_bg.gif +0 -0
  13. data/lib/subtrac/common/images/trac/saint_logo_small.png +0 -0
  14. data/lib/subtrac/common/static/404.html +14 -0
  15. data/lib/subtrac/common/styles/trac.css +222 -0
  16. data/lib/subtrac/common/trac.ini +178 -0
  17. data/lib/subtrac/config/config.yml +54 -0
  18. data/lib/subtrac/passwords +1 -0
  19. data/lib/subtrac/shared/trac.ini +178 -0
  20. data/lib/subtrac/templates/location.erb +16 -0
  21. data/lib/subtrac/templates/projects/blank/svn/branches/README +0 -0
  22. data/lib/subtrac/templates/projects/blank/svn/tags/README +0 -0
  23. data/lib/subtrac/templates/projects/blank/svn/trunk/README +0 -0
  24. data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +57 -0
  25. data/lib/subtrac/templates/projects/new/svn/trunk/trac/wiki/WikiStart +46 -0
  26. data/lib/subtrac/templates/projects/new/trac/wiki/WikiStart +23 -0
  27. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/index/index.html +22 -0
  28. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/layout.html +56 -0
  29. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/site.html +27 -0
  30. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/theme.html +86 -0
  31. data/lib/subtrac/templates/projects/trac_theme/trac/wiki/WikiStart +4 -0
  32. data/lib/subtrac/templates/trac.erb +25 -0
  33. data/lib/subtrac/templates/vhost.erb +35 -0
  34. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
  35. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +419 -0
  36. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +3 -0
  37. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +20 -0
  38. data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
  39. data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +28 -0
  40. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +168 -0
  41. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +137 -0
  42. data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +91 -0
  43. data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +199 -0
  44. data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +105 -0
  45. data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +287 -0
  46. data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +71 -0
  47. data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +4 -0
  48. data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +135 -0
  49. data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +70 -0
  50. data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +142 -0
  51. data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +231 -0
  52. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +27 -0
  53. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +152 -0
  54. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +160 -0
  55. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +124 -0
  56. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +134 -0
  57. data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +132 -0
  58. data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +97 -0
  59. data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +161 -0
  60. data/lib/subtrac/trac-plugins/clientsplugin/setup.py +43 -0
  61. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +4 -0
  62. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +273 -0
  63. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +44 -0
  64. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +36 -0
  65. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +32 -0
  66. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +409 -0
  67. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +30 -0
  68. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +53 -0
  69. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +181 -0
  70. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +66 -0
  71. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +47 -0
  72. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +93 -0
  73. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +86 -0
  74. data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +20 -0
  75. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +23 -0
  76. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +128 -0
  77. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +40 -0
  78. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +285 -0
  79. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +173 -0
  80. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
  81. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +164 -0
  82. data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +69 -0
  83. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +1 -0
  84. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +292 -0
  85. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +172 -0
  86. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +178 -0
  87. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +25 -0
  88. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +6 -0
  89. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +356 -0
  90. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +100 -0
  91. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +84 -0
  92. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +1149 -0
  93. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +119 -0
  94. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +73 -0
  95. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +165 -0
  96. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +28 -0
  97. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +221 -0
  98. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +675 -0
  99. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +150 -0
  100. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +25 -0
  101. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +131 -0
  102. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +84 -0
  103. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +104 -0
  104. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +194 -0
  105. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +62 -0
  106. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +28 -0
  107. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +127 -0
  108. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +129 -0
  109. data/lib/subtrac/trac-plugins/worklogplugin/setup.py +29 -0
  110. data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +1 -0
  111. data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +187 -0
  112. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +40 -0
  113. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +67 -0
  114. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +12 -0
  115. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +7 -0
  116. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +40 -0
  117. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +208 -0
  118. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +1439 -0
  119. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.png +0 -0
  120. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.xcf +0 -0
  121. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/worklogplugin.css +80 -0
  122. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstart.png +0 -0
  123. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstop.png +0 -0
  124. data/lib/subtrac/trac-plugins/worklogplugin/worklog/manager.py +336 -0
  125. data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +598 -0
  126. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +45 -0
  127. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +70 -0
  128. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +40 -0
  129. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +59 -0
  130. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +33 -0
  131. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +153 -0
  132. data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +96 -0
  133. data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +29 -0
  134. data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +31 -0
  135. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +47 -0
  136. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +174 -0
  137. data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +73 -0
  138. data/lib/subtrac/version.rb +4 -0
  139. 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
+ //})()
@@ -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
+ })()
@@ -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
+
@@ -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)
@@ -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
+