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,71 @@
1
+ from trac.core import *
2
+ from trac.perm import PermissionSystem
3
+ #from trac.ticket.model import AbstractEnum
4
+ #from trac.ticket.admin import AbstractEnumAdminPage
5
+ from trac.ticket.admin import TicketAdminPanel
6
+
7
+
8
+ from clients.events import ClientEvent, ClientEventsSystem
9
+
10
+ from trac.util.datefmt import utc, parse_date, get_date_format_hint, \
11
+ get_datetime_format_hint
12
+ from trac.web.chrome import add_link, add_script
13
+
14
+
15
+ class ClientEventsAdminPanel(TicketAdminPanel):
16
+
17
+ _type = 'clientevents'
18
+ _label = ('Client Events', 'Client Events')
19
+
20
+ # TicketAdminPanel methods
21
+ def _render_admin_panel(self, req, cat, page, event):
22
+ # Detail view?
23
+ if event:
24
+ clev = ClientEvent(self.env, event)
25
+ if req.method == 'POST':
26
+ if req.args.get('save'):
27
+ # Client Events are not saved... just deleted or viewed...
28
+ for option in clev.summary_options:
29
+ arg = 'summary-option-%s' % clev.summary_options[option]['md5']
30
+ clev.summary_options[option]['value'] = req.args.get(arg)
31
+ for option in clev.action_options:
32
+ arg = 'action-option-%s' % clev.action_options[option]['md5']
33
+ clev.action_options[option]['value'] = req.args.get(arg)
34
+ clev.update_options()
35
+ req.redirect(req.href.admin(cat, page))
36
+ elif req.args.get('cancel'):
37
+ req.redirect(req.href.admin(cat, page))
38
+
39
+ add_script(req, 'common/js/wikitoolbar.js')
40
+ data = {'view': 'detail', 'event': clev}
41
+
42
+ else:
43
+ if req.method == 'POST':
44
+ # Add Client
45
+ if req.args.get('add') and req.args.get('name'):
46
+ clev = ClientEvent(self.env)
47
+ clev.name = req.args.get('name')
48
+ clev.summary = req.args.get('summary')
49
+ clev.action = req.args.get('action')
50
+ clev.insert()
51
+ req.redirect(req.href.admin(cat, page))
52
+
53
+ # Remove clients
54
+ elif req.args.get('remove') and req.args.get('sel'):
55
+ sel = req.args.get('sel')
56
+ sel = isinstance(sel, list) and sel or [sel]
57
+ if not sel:
58
+ raise TracError('No client event selected')
59
+ db = self.env.get_db_cnx()
60
+ for name in sel:
61
+ clev = ClientEvent(self.env, name, db=db)
62
+ clev.delete(db=db)
63
+ db.commit()
64
+ req.redirect(req.href.admin(cat, page))
65
+
66
+ data = {'view': 'list',
67
+ 'events': ClientEvent.select(self.env),
68
+ 'summaries': ClientEventsSystem(self.env).get_summaries(),
69
+ 'actions': ClientEventsSystem(self.env).get_actions()}
70
+
71
+ return 'admin_client_events.html', data
@@ -0,0 +1,4 @@
1
+ fieldset.client
2
+ {
3
+ clear: both;
4
+ }
@@ -0,0 +1,135 @@
1
+ import re
2
+ import sys
3
+ import time
4
+ from datetime import date, datetime
5
+
6
+ from trac.attachment import Attachment
7
+ from trac.core import TracError
8
+ from trac.ticket.api import TicketSystem
9
+ from trac.util import sorted, embedded_numbers
10
+ from trac.util.datefmt import utc, utcmax, to_timestamp
11
+
12
+ __all__ = ['Client']
13
+
14
+ def simplify_whitespace(name):
15
+ """Strip spaces and remove duplicate spaces within names"""
16
+ return ' '.join(name.split())
17
+
18
+ class Client(object):
19
+
20
+ def __init__(self, env, name=None, db=None):
21
+ self.env = env
22
+ if name:
23
+ name = simplify_whitespace(name)
24
+ if name:
25
+ if not db:
26
+ db = self.env.get_db_cnx()
27
+ cursor = db.cursor()
28
+ cursor.execute("SELECT description,"
29
+ "default_rate, currency "
30
+ "FROM client "
31
+ "WHERE name=%s", (name,))
32
+ row = cursor.fetchone()
33
+ if not row:
34
+ raise TracError('Client %s does not exist.' % name)
35
+ self.name = self._old_name = name
36
+ self.description = row[0] or ''
37
+ self.default_rate = row[1] or ''
38
+ self.currency = row[2] or ''
39
+ else:
40
+ self.name = self._old_name = None
41
+ self.description = None
42
+ self.default_rate = ''
43
+ self.currency = ''
44
+
45
+ exists = property(fget=lambda self: self._old_name is not None)
46
+
47
+ def delete(self, db=None):
48
+ assert self.exists, 'Cannot deleting non-existent client'
49
+ if not db:
50
+ db = self.env.get_db_cnx()
51
+ handle_ta = True
52
+ else:
53
+ handle_ta = False
54
+
55
+ cursor = db.cursor()
56
+ self.env.log.info('Deleting client %s' % self.name)
57
+ cursor.execute("DELETE FROM client WHERE name=%s", (self.name,))
58
+
59
+ self.name = self._old_name = None
60
+
61
+ if handle_ta:
62
+ db.commit()
63
+
64
+ def insert(self, db=None):
65
+ assert not self.exists, 'Cannot insert existing client'
66
+ self.name = simplify_whitespace(self.name)
67
+ assert self.name, 'Cannot create client with no name'
68
+ if not db:
69
+ db = self.env.get_db_cnx()
70
+ handle_ta = True
71
+ else:
72
+ handle_ta = False
73
+
74
+ cursor = db.cursor()
75
+ self.env.log.debug("Creating new client '%s'" % self.name)
76
+ cursor.execute("INSERT INTO client (name, description,"
77
+ " default_rate, currency) "
78
+ "VALUES (%s,%s, %s,%s)",
79
+ (self.name, self.description,
80
+ self.default_rate, self.currency))
81
+
82
+ if handle_ta:
83
+ db.commit()
84
+
85
+ def update(self, db=None):
86
+ assert self.exists, 'Cannot update non-existent client'
87
+ self.name = simplify_whitespace(self.name)
88
+ assert self.name, 'Cannot update client with no name'
89
+ if not db:
90
+ db = self.env.get_db_cnx()
91
+ handle_ta = True
92
+ else:
93
+ handle_ta = False
94
+
95
+ cursor = db.cursor()
96
+ self.env.log.info('Updating client "%s"' % self.name)
97
+ cursor.execute("UPDATE client SET name=%s,description=%s,"
98
+ " default_rate=%s, currency=%s "
99
+ "WHERE name=%s",
100
+ (self.name, self.description,
101
+ self.default_rate, self.currency,
102
+ self._old_name))
103
+ if self.name != self._old_name:
104
+ # Update tickets
105
+ cursor.execute("UPDATE ticket_custom SET value=%s "
106
+ "WHERE name=%s AND value=%s",
107
+ (self.name, 'client', self._old_name))
108
+ # Update event options
109
+ cursor.execute("UPDATE client_event_summary_options SET client=%s "
110
+ "WHERE client=%s",
111
+ (self.name, self._old_name))
112
+ cursor.execute("UPDATE client_event_action_options SET client=%s "
113
+ "WHERE client=%s",
114
+ (self.name, self._old_name))
115
+ self._old_name = self.name
116
+
117
+ if handle_ta:
118
+ db.commit()
119
+
120
+ def select(cls, env, db=None):
121
+ if not db:
122
+ db = env.get_db_cnx()
123
+ cursor = db.cursor()
124
+ cursor.execute("SELECT name,description,"
125
+ " default_rate, currency "
126
+ "FROM client "
127
+ "ORDER BY name")
128
+ for name, description, default_rate, currency in cursor:
129
+ client = cls(env)
130
+ client.name = client._old_name = name
131
+ client.description = description or ''
132
+ client.default_rate = default_rate or ''
133
+ client.currency = currency or ''
134
+ yield client
135
+ select = classmethod(select)
@@ -0,0 +1,70 @@
1
+ from trac.core import *
2
+ from trac.wiki.api import IWikiMacroProvider, WikiSystem, parse_args
3
+ from trac.wiki import wiki_to_html
4
+ from trac.wiki.macros import WikiMacroBase
5
+ from trac.env import IEnvironmentSetupParticipant
6
+ from trac.wiki.parser import WikiParser
7
+ from StringIO import StringIO
8
+
9
+ class ClientWikiProcessor(Component):
10
+ implements(IWikiMacroProvider)
11
+
12
+ def __init__(self):
13
+ pass
14
+
15
+ def get_macros(self):
16
+ return ['client']
17
+
18
+ def get_macro_description(self, name):
19
+ return 'No real formatting but allows for easy extraction of specific text blocks designed to be displayed to the client'
20
+
21
+ def expand_macro(self, formatter, name, content):
22
+ return '<fieldset class="client"><legend>Comments to Client</legend>%s</fieldset>' % \
23
+ wiki_to_html(content, self.env, formatter.req)
24
+
25
+
26
+ def extract_client_text(text, sep="----\n"):
27
+ buf = StringIO()
28
+ stack = 0
29
+ gotblock = False
30
+ realsep = ''
31
+ for line in text.splitlines():
32
+ if stack:
33
+ realsep = sep
34
+ if line.strip() == WikiParser.ENDBLOCK:
35
+ stack = stack - 1
36
+ if stack:
37
+ buf.write(line + "\n")
38
+ realsep = ''
39
+ if gotblock:
40
+ if line.strip() == '#!client':
41
+ stack = stack + 1
42
+ if stack == 1:
43
+ buf.write(realsep)
44
+ else:
45
+ gotblock = False
46
+ elif line.strip() == WikiParser.STARTBLOCK:
47
+ gotblock = True
48
+ return buf.getvalue()
49
+
50
+ class TestProcessor(Component):
51
+ implements(IWikiMacroProvider)
52
+
53
+ def __init__(self):
54
+ pass
55
+
56
+ def get_macros(self):
57
+ return ['clientx']
58
+
59
+ def get_macro_description(self, name):
60
+ return 'Just a test'
61
+
62
+ def expand_macro(self, formatter, name, content):
63
+ db = self.env.get_db_cnx()
64
+ cursor = db.cursor()
65
+ cursor.execute("SELECT text FROM wiki WHERE name=%s ORDER BY version DESC LIMIT 1", ("WikiStart",))
66
+ try:
67
+ text = extract_client_text(cursor.fetchone()[0])
68
+ return wiki_to_html(text, self.env, formatter.req)
69
+ except:
70
+ return 'B0rken'
@@ -0,0 +1,142 @@
1
+ from trac.core import *
2
+
3
+
4
+ class CustomReportManager:
5
+ """A Class to manage custom reports"""
6
+ version = 1
7
+ name = "custom_report_manager_version"
8
+ env = None
9
+ log = None
10
+
11
+ def __init__(self, env, log):
12
+ self.env = env
13
+ self.log = log
14
+ self.upgrade()
15
+
16
+ def upgrade(self):
17
+ # Check to see what version we have
18
+ db = self.env.get_db_cnx()
19
+ cursor = db.cursor()
20
+ cursor.execute("SELECT value FROM system WHERE name=%s", (self.name,))
21
+ try:
22
+ version = int(cursor.fetchone()[0])
23
+ except:
24
+ version = 0
25
+ cursor.execute("INSERT INTO system (name,value) VALUES(%s,%s)",
26
+ (self.name, version))
27
+
28
+ if version > self.version:
29
+ raise TracError("Fatal Error: You appear to be running two plugins with conflicting versions "
30
+ "of the CustomReportManager class. Please ensure that '%s' is updated to "
31
+ "version %s of the file reportmanager.py (currently using version %s)."
32
+ % (__name__, str(version), str(self.version)))
33
+
34
+ # Do the staged updates
35
+ try:
36
+ if version < 1:
37
+ cursor.execute("CREATE TABLE custom_report ("
38
+ "id INTEGER,"
39
+ "uuid VARCHAR(64),"
40
+ "maingroup VARCHAR(255),"
41
+ "subgroup VARCHAR(255),"
42
+ "version INTEGER,"
43
+ "ordering INTEGER)")
44
+
45
+ #if version < 2:
46
+ # cursor.execute("...")
47
+
48
+ # Updates complete, set the version
49
+ cursor.execute("UPDATE system SET value=%s WHERE name=%s",
50
+ (self.version, self.name))
51
+ db.commit()
52
+ db.close()
53
+
54
+ except Exception, e:
55
+ self.log.error("CustomReportManager Exception: %s" % (e,));
56
+ db.rollback()
57
+
58
+ def add_report(self, title, author, description, query, uuid, version, maingroup, subgroup=""):
59
+ # First check to see if we can load an existing version of this report
60
+ db = self.env.get_db_cnx()
61
+ cursor = db.cursor()
62
+ try:
63
+ cursor.execute("SELECT id, version FROM custom_report "
64
+ "WHERE uuid=%s", (uuid,))
65
+ (id, currentversion) = cursor.fetchone()
66
+ except:
67
+ id = None
68
+ currentversion = 0
69
+
70
+ try:
71
+ if not id:
72
+ cursor.execute("SELECT MAX(id) FROM report")
73
+ next_id = int(cursor.fetchone()[0]) + 1
74
+ self.log.debug("Inserting new report with uuid '%s'" % (uuid,));
75
+
76
+ # Get the ordering of any current reports in this group/subgroup.
77
+ try:
78
+ cursor.execute("SELECT MAX(ordering) FROM custom_report "
79
+ "WHERE maingroup=%s AND subgroup=%s", (maingroup, subgroup))
80
+ ordering = int(cursor.fetchone()[0]) + 1
81
+ except:
82
+ ordering = 0
83
+
84
+ cursor.execute("INSERT INTO report (id, title, author, description, query) "
85
+ "VALUES (%s, %s, %s, %s, %s)",
86
+ (next_id, title, author, description, query))
87
+ cursor.execute("INSERT INTO custom_report (id, uuid, maingroup, subgroup, version, ordering) "
88
+ "VALUES (%s, %s, %s, %s, %s, %s)",
89
+ (next_id, uuid, maingroup, subgroup, version, ordering))
90
+ db.commit()
91
+ db.close()
92
+ return True
93
+ if currentversion < version:
94
+ self.log.debug("Updating report with uuid '%s' to version %s" % (uuid,version));
95
+ cursor.execute("UPDATE report SET title=%s, author=%s, description=%s, query=%s "
96
+ "WHERE id=%s", (title, author, description, query, id))
97
+ cursor.execute("UPDATE custom_report SET version=%s, maingroup=%s, subgroup=%s "
98
+ "WHERE id=%s", (version, maingroup, subgroup, id))
99
+ db.commit()
100
+ db.close()
101
+ return True
102
+ except Exception, e:
103
+ self.log.error("CustomReportManager Exception: %s" % (e,));
104
+ db.rollback()
105
+
106
+ return False
107
+
108
+ def get_report_by_uuid(self, uuid):
109
+ db = self.env.get_db_cnx()
110
+ cursor = db.cursor()
111
+ rv = None
112
+ try:
113
+ cursor.execute("SELECT report.id,report.title FROM custom_report "
114
+ "LEFT JOIN report ON custom_report.id=report.id "
115
+ "WHERE custom_report.uuid=%s", (uuid,))
116
+ row = cursor.fetchone()
117
+ rv = (row[0], row[1])
118
+ except:
119
+ pass
120
+
121
+ return rv
122
+
123
+ def get_reports_by_group(self, group):
124
+ db = self.env.get_db_cnx()
125
+ cursor = db.cursor()
126
+ rv = {}
127
+ try:
128
+ cursor.execute("SELECT custom_report.subgroup,report.id,report.title "
129
+ "FROM custom_report "
130
+ "LEFT JOIN report ON custom_report.id=report.id "
131
+ "WHERE custom_report.maingroup=%s "
132
+ "ORDER BY custom_report.subgroup,custom_report.ordering", (group,))
133
+ for subgroup,id,title in cursor:
134
+ if not rv.has_key(subgroup):
135
+ rv[subgroup] = { "title": subgroup,
136
+ "reports": [] }
137
+ rv[subgroup]["reports"].append( { "id": int(id), "title": title } )
138
+ except:
139
+ pass
140
+
141
+ return rv
142
+
@@ -0,0 +1,231 @@
1
+ # IF YOU ADD A NEW SECTION OF REPORTS, You will need to make
2
+ # sure that section is also added to the reports hashtable
3
+ # near the bottom
4
+
5
+ # Please try to keep this clean
6
+
7
+ billing_reports = [{
8
+ 'uuid': 'cd175fb0-0c74-48e4-816f-d72b4ba98fc1',
9
+ 'title': 'Client Work Summary',
10
+ 'description': '',
11
+ 'version': 1,
12
+ 'sql': """
13
+ SELECT
14
+ __group__, __style__, ticket, summary, newvalue as Work_added,
15
+ time, _ord
16
+ FROM(
17
+ SELECT '' as __style__, t.id as ticket,
18
+ SUM(CAST(newvalue as DECIMAL)) as newvalue, t.summary as summary,
19
+ MAX(ticket_change.time) as time, client.value as __group__, 0 as _ord
20
+ FROM ticket_change
21
+ JOIN ticket t on t.id = ticket_change.ticket
22
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
23
+ and billable.name = 'billable'
24
+ LEFT JOIN ticket_custom as client on client.ticket = t.id
25
+ and client.name = 'client'
26
+ WHERE field = 'hours' and
27
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
28
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
29
+ AND ticket_change.time >= $STARTDATE
30
+ AND ticket_change.time < $ENDDATE
31
+ GROUP BY client.value, t.id, t.summary
32
+
33
+ UNION
34
+
35
+ SELECT 'background-color:#DFE;' as __style__, NULL as ticket,
36
+ sum(CAST(newvalue as DECIMAL)) as newvalue, 'Total work done' as summary,
37
+ NULL as time, client.value as __group__, 1 as _ord
38
+ FROM ticket_change
39
+ JOIN ticket t on t.id = ticket_change.ticket
40
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
41
+ and billable.name = 'billable'
42
+ LEFT JOIN ticket_custom as client on client.ticket = t.id
43
+ and client.name = 'client'
44
+ WHERE field = 'hours' and
45
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
46
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
47
+ AND ticket_change.time >= $STARTDATE
48
+ AND ticket_change.time < $ENDDATE
49
+ GROUP By client.value
50
+ ) as tbl
51
+ ORDER BY __group__, _ord ASC, ticket, time
52
+ """
53
+ }]
54
+
55
+ ticket_hours_reports = [{
56
+ 'uuid': 'e2ab8124-9309-4d1c-9cec-f8c7b11bf579',
57
+ 'title': 'Ticket Hours Grouped By Client',
58
+ 'description': '',
59
+ 'version': 1,
60
+ 'sql': """
61
+ SELECT __color__, __group__, __style__, ticket, summary, __component__ ,version,
62
+ severity, milestone, status, owner, estimate_work, total_work, billable,
63
+ _ord
64
+
65
+ FROM (
66
+ SELECT p.value AS __color__,
67
+ client.value AS __group__,
68
+ '' as __style__,
69
+ t.id AS ticket, summary AS summary, -- ## Break line here
70
+ component as __component__,version, severity, milestone, status, owner,
71
+ CAST(estimatedhours.value as DECIMAL) as estimate_work,
72
+ CAST(totalhours.value as DECIMAL) as Total_work,
73
+ CASE WHEN billable.value = 1 THEN 'Y'
74
+ else 'N'
75
+ END as billable,
76
+ time AS created, changetime AS modified, -- ## Dates are formatted
77
+ description AS _description_, -- ## Uses a full row
78
+ changetime AS _changetime,
79
+ reporter AS _reporter
80
+ ,0 as _ord
81
+
82
+ FROM ticket as t
83
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
84
+
85
+ LEFT JOIN ticket_custom as estimatedhours ON estimatedhours.name='estimatedhours'
86
+ AND estimatedhours.ticket = t.id
87
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
88
+ AND totalhours.ticket = t.id
89
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
90
+ AND billable.ticket = t.id
91
+ LEFT JOIN ticket_custom as client ON client.name='client'
92
+ AND client.ticket = t.id
93
+
94
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
95
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
96
+
97
+
98
+ UNION
99
+
100
+ SELECT '1' AS __color__,
101
+ client.value AS __group__,
102
+ 'background-color:#DFE;' as __style__,
103
+ NULL as ticket, 'Total work' AS summary,
104
+ t.component as __component__, NULL as version, NULL as severity,
105
+ NULL as milestone, NULL as status,
106
+ NULL as owner,
107
+ SUM(CAST(estimatedhours.value as DECIMAL)) as estimate_work,
108
+ SUM(CAST(totalhours.value as DECIMAL)) as Total_work,
109
+ NULL as billable,
110
+ NULL as created,
111
+ NULL as modified, -- ## Dates are formatted
112
+
113
+ NULL AS _description_,
114
+ NULL AS _changetime,
115
+ NULL AS _reporter
116
+ ,1 as _ord
117
+ FROM ticket as t
118
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
119
+
120
+ LEFT JOIN ticket_custom as estimatedhours ON estimatedhours.name='estimatedhours'
121
+ AND estimatedhours.ticket = t.id
122
+
123
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
124
+ AND totalhours.ticket = t.id
125
+
126
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
127
+ AND billable.ticket = t.id
128
+
129
+ LEFT JOIN ticket_custom as client ON client.name='client'
130
+ AND client.ticket = t.id
131
+
132
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
133
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
134
+ GROUP BY client.value
135
+ ) as tbl
136
+ ORDER BY __group__, _ord ASC,ticket
137
+ """
138
+ },{
139
+ 'uuid': '9194d297-bd5a-4225-a28c-e981525b20e1',
140
+ 'title': 'Ticket Hours Grouped By Client with Description',
141
+ 'description': '',
142
+ 'version': 1,
143
+ 'sql': """
144
+ SELECT __color__, __group__, __style__, ticket, summary, __component__ ,version,
145
+ severity, milestone, status, owner, estimate_work, total_work, billable,
146
+ _description_, _ord
147
+
148
+ FROM (
149
+ SELECT p.value AS __color__,
150
+ client.value AS __group__,
151
+ '' as __style__,
152
+ t.id AS ticket, summary AS summary, -- ## Break line here
153
+ component as __component__,version, severity, milestone, status, owner,
154
+ CAST(estimatedhours.value as DECIMAL) as estimate_work,
155
+ CAST(totalhours.value as DECIMAL) as Total_work,
156
+ CASE WHEN billable.value = 1 THEN 'Y'
157
+ else 'N'
158
+ END as billable,
159
+ time AS created, changetime AS modified, -- ## Dates are formatted
160
+ description AS _description_, -- ## Uses a full row
161
+ changetime AS _changetime,
162
+ reporter AS _reporter
163
+ ,0 as _ord
164
+
165
+ FROM ticket as t
166
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
167
+
168
+ LEFT JOIN ticket_custom as estimatedhours ON estimatedhours.name='estimatedhours'
169
+ AND estimatedhours.ticket = t.id
170
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
171
+ AND totalhours.ticket = t.id
172
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
173
+ AND billable.ticket = t.id
174
+ LEFT JOIN ticket_custom as client ON client.name='client'
175
+ AND client.ticket = t.id
176
+
177
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
178
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
179
+
180
+
181
+ UNION
182
+
183
+ SELECT '1' AS __color__,
184
+ client.value AS __group__,
185
+ 'background-color:#DFE;' as __style__,
186
+ NULL as ticket, 'Total work' AS summary,
187
+ t.component as __component__, NULL as version, NULL as severity,
188
+ NULL as milestone, NULL as status,
189
+ NULL as owner,
190
+ SUM(CAST(estimatedhours.value as DECIMAL)) as estimate_work,
191
+ SUM(CAST(totalhours.value as DECIMAL)) as Total_work,
192
+ NULL as billable,
193
+ NULL as created,
194
+ NULL as modified, -- ## Dates are formatted
195
+
196
+ NULL AS _description_,
197
+ NULL AS _changetime,
198
+ NULL AS _reporter
199
+ ,1 as _ord
200
+ FROM ticket as t
201
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
202
+
203
+ LEFT JOIN ticket_custom as estimatedhours ON estimatedhours.name='estimatedhours'
204
+ AND estimatedhours.ticket = t.id
205
+
206
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
207
+ AND totalhours.ticket = t.id
208
+
209
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
210
+ AND billable.ticket = t.id
211
+
212
+ LEFT JOIN ticket_custom as client ON client.name='client'
213
+ AND client.ticket = t.id
214
+
215
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
216
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
217
+ GROUP BY client.value
218
+ ) as tbl
219
+ ORDER BY __group__, _ord ASC,ticket
220
+ """
221
+ }]
222
+
223
+
224
+ reports = [{
225
+ 'title': 'Billing Reports',
226
+ 'reports': billing_reports
227
+ },{
228
+ 'title': 'Ticket/Hour Reports',
229
+ 'reports': ticket_hours_reports
230
+ }]
231
+