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,45 @@
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+ <html xmlns="http://www.w3.org/1999/xhtml"
5
+ xmlns:xi="http://www.w3.org/2001/XInclude"
6
+ xmlns:py="http://genshi.edgewall.org/">
7
+ <xi:include href="layout.html" />
8
+ <xi:include href="macros.html" />
9
+ <head>
10
+ <title>Work Log</title>
11
+ </head>
12
+
13
+ <body>
14
+ <form method="post" action="${worklog_href}">
15
+ <div id="content" class="worklog">
16
+ <a id="worklogmanual" href="${usermanual_href}" >${usermanual_title}</a>
17
+ <h2>Work Log Summary</h2>
18
+ <div id="messages" >
19
+ <div py:for="item in messages" class="message" >${item}</div>
20
+ </div>
21
+
22
+ <table border="0" cellspacing="0" cellpadding="0" id="worklog_report">
23
+ <tr>
24
+ <th>User</th>
25
+ <th>Activity</th>
26
+ <th>Time</th>
27
+ <th>Comment</th>
28
+ </tr>
29
+ <tr py:for="log in worklog">
30
+ <td><a href="${worklog_href}/users/${log.user}">${log.dispname}</a></td>
31
+ <py:choose>
32
+ <td py:when="log.endtime==0"><a class="${log.status} ticket" href="${ticket_href}/${log.ticket}">#${log.ticket}</a>: ${log.summary}</td>
33
+ <td py:otherwise=""><em>Idle</em> <small>(Last worked on: <a class="${log.status} ticket" href="${ticket_href}/${log.ticket}">#${log.ticket}</a>: ${log.summary})</small></td>
34
+ </py:choose>
35
+ <td><span id="worklog_time_delta">${log.delta}</span></td>
36
+ <td><span id="worklog_comment">${log.comment}</span></td>
37
+ </tr>
38
+ </table>
39
+ </div>
40
+ </form>
41
+
42
+ <div id="altlinks"> <h3>Download in other formats:</h3><ul><li class="first last"><a href="?format=csv" class="csv">CSV</a></li></ul></div>
43
+ </body>
44
+
45
+ </html>
@@ -0,0 +1,70 @@
1
+ <html xmlns="http://www.w3.org/1999/xhtml"
2
+ xmlns:py="http://genshi.edgewall.org/"
3
+ xmlns:xi="http://www.w3.org/2001/XInclude" py:strip="">
4
+ <py:choose>
5
+ <py:when test="xhr">
6
+ <py:match path="head" once="true"><head>
7
+ <title py:with="title = list(select('title/text()'))">
8
+ <py:if test="title">${title} –</py:if>
9
+ ${' – '.join(filter(None, [project.name, 'Trac']))}
10
+ </title>
11
+ <py:if test="chrome.links">
12
+ <py:for each="rel, links in chrome.links.items()">
13
+ <link rel="${rel}" py:for="link in links" py:attrs="link" />
14
+ </py:for>
15
+ </py:if>
16
+ <py:if test="'SEARCH_VIEW' in perm" id="search">
17
+ <link type="application/opensearchdescription+xml" rel="search"
18
+ href="${href.search('opensearch')}" title="Search $project.name"/>
19
+ </py:if>
20
+ <script py:for="script in chrome.scripts"
21
+ type="${script.type}" src="${script.href}"></script>
22
+ ${Markup('&lt;!--[if lt IE 7]&gt;')}
23
+ <script type="text/javascript" src="${chrome.htdocs_location}js/ie_pre7_hacks.js"></script>
24
+ ${Markup('&lt;![endif]--&gt;')}
25
+ ${select("*[local-name() != 'title']")}
26
+ </head></py:match>
27
+ </py:when>
28
+ <py:otherwise>
29
+ <xi:include href="layout.html" />
30
+ </py:otherwise>
31
+ </py:choose>
32
+ <xi:include href="macros.html" />
33
+ <head>
34
+ <title>Work Log</title>
35
+ </head>
36
+ <body>
37
+ <div>
38
+ <div py:if="xhr" style="text-align: right;">
39
+ <span style="text-decoration: underline; color: blue; cursor: pointer;" class="jqmClose">close</span>
40
+ </div>
41
+ <form method="post" action="${worklog_href}" class="inlinebuttons">
42
+ <input type="hidden" name="source_url" value="${ticket_href}" />
43
+ <input type="hidden" name="ticket" value="${ticket}" />
44
+ <input id="worklogStoptime" type="hidden" name="stoptime" value="" />
45
+ <fieldset>
46
+ <legend>Stop work</legend>
47
+ <div class="field">
48
+ <fieldset class="iefix">
49
+ <label for="worklogComment">Optional: Leave a comment about the work you have done...</label>
50
+ <p><textarea id="worklogComment" name="comment" class="wikitext" rows="6" cols="60"></textarea></p>
51
+ </fieldset>
52
+ </div>
53
+ <div class="field">
54
+ <label>Override end time</label>
55
+ <div align="center">
56
+ <div style="width: 185px;">
57
+ <div id="worklogStopDate"></div>
58
+ <br clear="all" />
59
+ <div style="text-align: right;">
60
+ &nbsp;&nbsp;@&nbsp;<input id="worklogStopTime" type="text" size="6" />
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <div style="text-align: right;"><input id="worklogSubmit" type="submit" name="${action}work" value="${label}" /></div>
66
+ </fieldset>
67
+ </form>
68
+ </div>
69
+ </body>
70
+ </html>
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+ <html xmlns="http://www.w3.org/1999/xhtml"
5
+ xmlns:xi="http://www.w3.org/2001/XInclude"
6
+ xmlns:py="http://genshi.edgewall.org/">
7
+ <xi:include href="layout.html" />
8
+ <xi:include href="macros.html" />
9
+ <head>
10
+ <title>Work Log</title>
11
+ </head>
12
+
13
+ <body>
14
+ <form method="post" action="${worklog_href}">
15
+ <div id="content" class="worklog">
16
+ <a id="worklogmanual" href="${usermanual_href}" >${usermanual_title}</a>
17
+ <h2>Work Log for ${worklog[0].dispname}</h2>
18
+ <div id="messages" >
19
+ <div py:for="item in messages" class="message" >${item}</div>
20
+ </div>
21
+
22
+ <table border="0" cellspacing="0" cellpadding="0" id="worklog_report">
23
+ <tr>
24
+ <th>Ticket</th>
25
+ <th>Time</th>
26
+ <th>Comment</th>
27
+ </tr>
28
+ <tr py:for="log in worklog">
29
+ <td><a class="${log.status} ticket" href="${ticket_href}/${log.ticket}">#${log.ticket}</a>: ${log.summary}</td>
30
+ <td><span id="worklog_time_delta">${log.delta}</span></td>
31
+ <td><span id="worklog_comment">${log.comment}</span></td>
32
+ </tr>
33
+ </table>
34
+ </div>
35
+ </form>
36
+
37
+ <div id="altlinks"> <h3>Download in other formats:</h3><ul><li class="first last"><a href="?format=csv" class="csv">CSV</a></li></ul></div>
38
+ </body>
39
+
40
+ </html>
@@ -0,0 +1,59 @@
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+ <html xmlns="http://www.w3.org/1999/xhtml"
5
+ xmlns:xi="http://www.w3.org/2001/XInclude"
6
+ xmlns:py="http://genshi.edgewall.org/">
7
+ <xi:include href="admin.html" />
8
+ <head>
9
+ <title>Work Log Settings</title>
10
+ </head>
11
+
12
+ <body>
13
+ <h2>Work Log Settings</h2>
14
+
15
+ <py:choose test="view">
16
+ <form py:when="'settings'" method="post">
17
+ <fieldset>
18
+ <legend>Options:</legend>
19
+ <div class="field">
20
+ <input type="checkbox" id="timingandestimation" name="timingandestimation" value="1" checked="${timingandestimation or None}" />
21
+ <label for="timingandestimation">Record time via <a href="http://www.trac-hacks.org/wiki/TimingAndEstimationPlugin">Timing and Estimation Plugin</a>?</label>
22
+ </div>
23
+ <div class="field">
24
+ <input type="checkbox" id="comment" name="comment" value="1" checked="${comment or None}" />
25
+ <label for="comment">Automatically add a comment when you stop work on a ticket?</label><br />
26
+ <small>Please note that if you leave a comment when you stop work, then a comment is always added to the ticket to ensure all information is present on the ticket.</small>
27
+ </div>
28
+ <div class="field">
29
+ <input type="checkbox" id="autostop" name="autostop" value="1" checked="${autostop or None}" />
30
+ <label for="autostop">Stop work automatically if ticket is closed?</label>
31
+ </div>
32
+ <div class="field">
33
+ <input type="checkbox" id="autoreassignaccept" name="autoreassignaccept" value="1" checked="${autoreassignaccept or None}" />
34
+ <label for="autoreassignaccept">Automatically reassign and accept (if necessary) when starting work?</label>
35
+ </div>
36
+ <div class="field">
37
+ <input type="checkbox" id="autostopstart" name="autostopstart" value="1" checked="${autostopstart or None}" />
38
+ <label for="autostopstart">Allow users to start working on a different ticket (i.e. automatically stop working on current ticket)?</label>
39
+ </div>
40
+ </fieldset>
41
+ <br />
42
+ <fieldset>
43
+ <legend>Parameters:</legend>
44
+ <div class="field">
45
+ <label for="roundup">Round up to nearest minute</label>&nbsp;
46
+ <input type="text" id="roundup" name="roundup" size="4" value="${roundup}" /><br />
47
+ <small>This only applies when integrating with the
48
+ <a href="http://www.trac-hacks.org/wiki/TimingAndEstimationPlugin">Timing and Estimation Plugin</a></small>
49
+ </div>
50
+ </fieldset>
51
+
52
+ <div class="buttons">
53
+ <input type="submit" name="update" value="Update Settings" />
54
+ </div>
55
+ </form>
56
+ </py:choose>
57
+ </body>
58
+
59
+ </html>
@@ -0,0 +1,33 @@
1
+ from trac.ticket import ITicketChangeListener, Ticket
2
+ from trac.core import *
3
+ from manager import WorkLogManager
4
+
5
+ class WorkLogTicketObserver(Component):
6
+ implements(ITicketChangeListener)
7
+ def __init__(self):
8
+ pass
9
+
10
+ def ticket_created(self, ticket):
11
+ """Called when a ticket is created."""
12
+ pass
13
+
14
+ def ticket_changed(self, ticket, comment, author, old_values):
15
+ """Called when a ticket is modified.
16
+
17
+ `old_values` is a dictionary containing the previous values of the
18
+ fields that have changed.
19
+ """
20
+ if self.config.getbool('worklog', 'autostop') \
21
+ and 'closed' == ticket['status'] \
22
+ and old_values.has_key('status') \
23
+ and 'closed' != old_values['status']:
24
+ mgr = WorkLogManager(self.env, self.config)
25
+ who,since = mgr.who_is_working_on(ticket.id)
26
+ if who:
27
+ mgr = WorkLogManager(self.env, self.config, who)
28
+ mgr.stop_work()
29
+
30
+ def ticket_deleted(self, ticket):
31
+ """Called when a ticket is deleted."""
32
+ pass
33
+
@@ -0,0 +1,153 @@
1
+ import re
2
+ from util import *
3
+ from trac.core import *
4
+ from trac.web.api import ITemplateStreamFilter
5
+ from trac.web.chrome import add_stylesheet, add_script
6
+ from trac.wiki import wiki_to_oneliner
7
+
8
+ from manager import WorkLogManager
9
+ from util import pretty_timedelta
10
+
11
+ from genshi import XML
12
+ from genshi.builder import tag
13
+ from genshi.filters.transform import Transformer
14
+
15
+ class WorkLogTicketAddon(Component):
16
+ implements(ITemplateStreamFilter)
17
+
18
+ def __init__(self):
19
+ pass
20
+
21
+ def get_task_markup(self, req, ticket, task):
22
+ if not task:
23
+ return ''
24
+
25
+ ticket_text = 'ticket #' + str(task['ticket'])
26
+ if task['ticket'] == ticket:
27
+ ticket_text = 'this ticket'
28
+ timedelta = pretty_timedelta(datetime.fromtimestamp(task['starttime']), None);
29
+
30
+ return '<li>%s</li>' % wiki_to_oneliner('You have been working on %s for %s' % (ticket_text, timedelta), self.env, req=req)
31
+
32
+
33
+ def get_ticket_markup(self, who, since):
34
+ timedelta = pretty_timedelta(datetime.fromtimestamp(since), None);
35
+ return '<li>%s has been working on this ticket for %s</li>' % (who, timedelta)
36
+
37
+
38
+ def get_ticket_markup_noone(self):
39
+ return '<li>Nobody is working on this ticket</li>'
40
+
41
+
42
+ def get_button_markup(self, req, ticket, stop=False):
43
+ if stop:
44
+ action = 'stop'
45
+ label = 'Stop Work'
46
+ else:
47
+ action = 'start'
48
+ label = 'Start Work'
49
+
50
+ return '''
51
+ <form id="worklogTicketForm" method="post" action="%s" class="inlinebuttons" onsubmit="return tracWorklog.%s();">
52
+ <input type="hidden" name="source_url" value="%s" />
53
+ <input type="hidden" name="ticket" value="%s" />
54
+ <input type="submit" name="%swork" value="%s" />
55
+ </form>
56
+ <div id="worklogPopup" class="jqmWindow">
57
+ <div style="text-align: right;">
58
+ <span style="text-decoration: underline; color: blue; cursor: pointer;" class="jqmClose">close</span>
59
+ </div>
60
+ <form method="post" action="%s" class="inlinebuttons">
61
+ <input type="hidden" name="source_url" value="%s" />
62
+ <input type="hidden" name="ticket" value="%s" />
63
+ <input id="worklogStoptime" type="hidden" name="stoptime" value="" />
64
+ <fieldset>
65
+ <legend>Stop work</legend>
66
+ <div class="field">
67
+ <fieldset class="iefix">
68
+ <label for="worklogComment">Optional: Leave a comment about the work you have done...</label>
69
+ <p><textarea id="worklogComment" name="comment" class="wikitext" rows="6" cols="60"></textarea></p>
70
+ </fieldset>
71
+ </div>
72
+ <div class="field">
73
+ <label>Override end time</label>
74
+ <div align="center">
75
+ <div style="width: 185px;">
76
+ <div id="worklogStopDate"></div>
77
+ <br clear="all" />
78
+ <div style="text-align: right;">
79
+ &nbsp;&nbsp;@&nbsp;<input id="worklogStopTime" type="text" size="6" />
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ <div style="text-align: right;"><input id="worklogSubmit" type="submit" name="%swork" value="%s" /></div>
85
+ </fieldset>
86
+ </form>
87
+ </div>
88
+ ''' % (req.href.worklog(), action,
89
+ req.href.ticket(ticket),
90
+ ticket,
91
+ action, label,
92
+ req.href.worklog(),
93
+ req.href.ticket(ticket),
94
+ ticket,
95
+ action, label)
96
+
97
+
98
+ # ITemplateStreamFilter methods
99
+ def filter_stream(self, req, method, filename, stream, data):
100
+ match = re.match(r'/ticket/([0-9]+)$', req.path_info)
101
+ if match and req.perm.has_permission('WORK_LOG'):
102
+ ticket = int(match.group(1))
103
+ add_stylesheet(req, "worklog/worklogplugin.css")
104
+
105
+ add_script(req, 'worklog/jqModal.js')
106
+ add_stylesheet(req, 'worklog/jqModal.css')
107
+
108
+ add_script(req, 'worklog/ui.datepicker.js')
109
+ add_stylesheet(req, 'worklog/ui.datepicker.css')
110
+
111
+ add_script(req, 'worklog/jquery.mousewheel.pack.js')
112
+ add_script(req, 'worklog/jquery.timeentry.pack.js')
113
+
114
+ add_script(req, 'worklog/tracWorklog.js')
115
+
116
+ mgr = WorkLogManager(self.env, self.config, req.authname)
117
+ task_markup = ''
118
+ if req.authname != 'anonymous':
119
+ task = mgr.get_active_task()
120
+ if task:
121
+ task_markup = self.get_task_markup(req, ticket, task)
122
+
123
+ who,since = mgr.who_is_working_on(ticket)
124
+ ticket_markup = ''
125
+ if who:
126
+ # If who == req.authname then we will have some text from above.
127
+ if who != req.authname:
128
+ ticket_markup = self.get_ticket_markup(who, since)
129
+ else:
130
+ ticket_markup = self.get_ticket_markup_noone()
131
+
132
+ button_markup = ''
133
+ if req.authname != 'anonymous':
134
+ if mgr.can_work_on(ticket):
135
+ # Display a "Work on Link" button.
136
+ button_markup = self.get_button_markup(req, ticket)
137
+ elif task and task['ticket'] == ticket:
138
+ # We are currently working on this, so display the stop button...
139
+ button_markup = self.get_button_markup(req, ticket, True)
140
+
141
+ # User's current task information
142
+ html = XML('''
143
+ <fieldset class="workloginfo">
144
+ <legend>Work Log</legend>
145
+ %s
146
+ <ul>
147
+ %s
148
+ %s
149
+ </ul>
150
+ </fieldset>
151
+ ''' % (button_markup, task_markup, ticket_markup))
152
+ stream |= Transformer('.//div[@id="ticket"]').before(html)
153
+ return stream
@@ -0,0 +1,96 @@
1
+ import re
2
+ from genshi.builder import tag
3
+ from util import *
4
+ from trac.log import logger_factory
5
+ from trac.core import *
6
+ from trac.util.datefmt import to_timestamp, utc
7
+ from trac.util.text import shorten_line
8
+ from trac.ticket.api import TicketSystem
9
+ from trac.timeline.api import ITimelineEventProvider
10
+ from trac.wiki.formatter import format_to_oneliner
11
+ from trac.resource import Resource, get_resource_url, \
12
+ render_resource_link, get_resource_shortname, \
13
+ get_resource_name
14
+ from trac.web.chrome import add_stylesheet
15
+
16
+ class WorkLogTimelineAddon(Component):
17
+ implements(ITimelineEventProvider)
18
+
19
+ # ITimelineEventProvider methods
20
+
21
+ def get_timeline_filters(self, req):
22
+ if req.perm.has_permission('WORK_VIEW'):
23
+ yield ('workstart', 'Work started', True)
24
+ yield ('workstop', 'Work stopped', True)
25
+
26
+ def get_timeline_events(self, req, start, stop, filters):
27
+ # Worklog changes
28
+ show_starts = 'workstart' in filters
29
+ show_stops = 'workstop' in filters
30
+ if show_starts or show_stops:
31
+ add_stylesheet(req, "worklog/worklogplugin.css")
32
+
33
+ ts_start = to_timestamp(start)
34
+ ts_stop = to_timestamp(stop)
35
+
36
+ ticket_realm = Resource('ticket')
37
+ db = self.env.get_db_cnx()
38
+ cursor = db.cursor()
39
+
40
+ cursor.execute("""SELECT wl.worker,wl.ticket,wl.time,wl.starttime,wl.comment,wl.kind,t.summary,t.status,t.resolution,t.type
41
+ FROM (
42
+
43
+ SELECT worker, ticket, starttime AS time, starttime, comment, 'start' AS kind
44
+ FROM work_log
45
+
46
+ UNION
47
+
48
+ SELECT worker, ticket, endtime AS time, starttime, comment, 'stop' AS kind
49
+ FROM work_log
50
+
51
+ ) AS wl
52
+ INNER JOIN ticket t ON t.id = wl.ticket
53
+ AND wl.time>=%s AND wl.time<=%s
54
+ ORDER BY wl.time"""
55
+ % (ts_start, ts_stop))
56
+ previous_update = None
57
+ for worker,tid,ts,ts_start,comment,kind,summary,status,resolution,type in cursor:
58
+ ticket = ticket_realm(id=tid)
59
+ time = datetime.fromtimestamp(ts, utc)
60
+ started = None
61
+ if kind == 'start':
62
+ if not show_starts:
63
+ continue
64
+ yield ('workstart', time, worker, (ticket,summary,status,resolution,type, started, ""))
65
+ else:
66
+ if not show_stops:
67
+ continue
68
+ started = datetime.fromtimestamp(ts_start, utc)
69
+ if comment:
70
+ comment = "(Time spent: %s)[[BR]]%s" % (pretty_timedelta(started, time), comment)
71
+ else:
72
+ comment = '(Time spent: %s)' % pretty_timedelta(started, time)
73
+ yield ('workstop', time, worker, (ticket,summary,status,resolution,type, started, comment))
74
+
75
+ def render_timeline_event(self, context, field, event):
76
+ ticket,summary,status,resolution,type, started, comment = event[3]
77
+ if field == 'url':
78
+ return context.href.ticket(ticket.id)
79
+ elif field == 'title':
80
+ title = TicketSystem(self.env).format_summary(summary, status,
81
+ resolution, type)
82
+ return tag('Work ', started and 'stopped' or 'started',
83
+ ' on Ticket ', tag.em('#', ticket.id, title=title),
84
+ ' (', shorten_line(summary), ') ')
85
+ elif field == 'description':
86
+ if self.config['timeline'].getbool('abbreviated_messages'):
87
+ comment = shorten_line(comment)
88
+ markup = format_to_oneliner(self.env, context(resource=ticket),
89
+ comment)
90
+ #if wiki_page.version > 1:
91
+ # diff_href = context.href.wiki(
92
+ # wiki_page.id, version=wiki_page.version, action='diff')
93
+ # markup = tag(markup, ' ', tag.a('(diff)', href=diff_href))
94
+ return markup
95
+
96
+