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,199 @@
1
+ import time
2
+ from trac.core import *
3
+ from trac.env import IEnvironmentSetupParticipant
4
+ from reportmanager import CustomReportManager
5
+
6
+ class ClientsSetupParticipant(Component):
7
+ implements(IEnvironmentSetupParticipant)
8
+
9
+ db_version_key = None
10
+ db_version = None
11
+ db_installed_version = None
12
+
13
+ def __init__(self):
14
+ self.db_version_key = 'clients_plugin_version'
15
+ self.db_version = 5
16
+ self.db_installed_version = None
17
+
18
+ # Initialise database schema version tracking.
19
+ db = self.env.get_db_cnx()
20
+ cursor = db.cursor()
21
+ cursor.execute("SELECT value FROM system WHERE name=%s", (self.db_version_key,))
22
+ try:
23
+ self.db_installed_version = int(cursor.fetchone()[0])
24
+ except:
25
+ self.db_installed_version = 0
26
+ cursor.execute("INSERT INTO system (name,value) VALUES(%s,%s)",
27
+ (self.db_version_key, self.db_installed_version))
28
+ db.commit()
29
+ db.close()
30
+
31
+ def system_needs_upgrade(self):
32
+ return self.db_installed_version < self.db_version
33
+
34
+ def do_db_upgrade(self):
35
+ db = self.env.get_db_cnx()
36
+ cursor = db.cursor()
37
+
38
+ # Do the staged updates
39
+ try:
40
+ if self.db_installed_version < 2:
41
+ print 'Creating client table'
42
+ cursor.execute('CREATE TABLE client ('
43
+ 'name TEXT,'
44
+ 'description TEXT,'
45
+ 'changes_list TEXT,'
46
+ 'changes_period TEXT,'
47
+ 'changes_lastupdate INTEGER,'
48
+ 'summary_list TEXT,'
49
+ 'summary_period TEXT,'
50
+ 'summary_lastupdate INTEGER'
51
+ ')')
52
+ # Import old Enums
53
+ cursor.execute('INSERT INTO client (name) '
54
+ 'SELECT name FROM enum WHERE type=%s', ('client',))
55
+ # Clean them out
56
+ cursor.execute('DELETE FROM enum WHERE type=%s', ('client',))
57
+
58
+ if self.db_installed_version < 3:
59
+ print 'Updating clients table (v3)'
60
+ cursor.execute('ALTER TABLE client '
61
+ 'ADD COLUMN default_rate INTEGER')
62
+ cursor.execute('ALTER TABLE client '
63
+ 'ADD COLUMN currency TEXT')
64
+
65
+ if self.db_installed_version < 4:
66
+ print 'Updating clients table (v4)'
67
+ cursor.execute('CREATE TABLE client_events ('
68
+ 'name TEXT,'
69
+ 'summary TEXT,'
70
+ 'action TEXT,'
71
+ 'lastrun INTEGER'
72
+ ')')
73
+ cursor.execute('CREATE TABLE client_event_summary_options ('
74
+ 'client_event TEXT,'
75
+ 'client TEXT,'
76
+ 'name TEXT,'
77
+ 'value TEXT'
78
+ ')')
79
+ cursor.execute('CREATE TABLE client_event_action_options ('
80
+ 'client_event TEXT,'
81
+ 'client TEXT,'
82
+ 'name TEXT,'
83
+ 'value TEXT'
84
+ ')')
85
+
86
+ if self.db_installed_version < 5:
87
+ print 'Updating clients table (v5)'
88
+ cursor.execute('INSERT INTO client_events '
89
+ 'SELECT "Weekly Summary", "Milestone Summary", "Send Email", MAX(summary_lastupdate) '
90
+ 'FROM client')
91
+ cursor.execute('INSERT INTO client_event_action_options '
92
+ 'SELECT "Weekly Summary", name, "Email Addresses", summary_list '
93
+ 'FROM client '
94
+ 'WHERE summary_list!=""')
95
+ cursor.execute('INSERT INTO client_events '
96
+ 'SELECT "Ticket Changes", "Ticket Change Summary", "Send Email", MAX(changes_lastupdate) '
97
+ 'FROM client')
98
+ cursor.execute('INSERT INTO client_event_action_options '
99
+ 'SELECT "Ticket Changes", name, "Email Addresses", changes_list '
100
+ 'FROM client '
101
+ 'WHERE changes_list!=""')
102
+ cursor.execute('CREATE TEMPORARY TABLE client_tmp ('
103
+ 'name TEXT,'
104
+ 'description TEXT,'
105
+ 'default_rate INTEGER,'
106
+ 'currency TEXT'
107
+ ')')
108
+ cursor.execute('INSERT INTO client_tmp SELECT name, description, default_rate, currency FROM client')
109
+ cursor.execute('DROP TABLE client')
110
+ cursor.execute('CREATE TABLE client ('
111
+ 'name TEXT,'
112
+ 'description TEXT,'
113
+ 'default_rate INTEGER,'
114
+ 'currency TEXT'
115
+ ')')
116
+ cursor.execute('INSERT INTO client SELECT name, description, default_rate, currency FROM client_tmp')
117
+ cursor.execute('DROP TABLE client_tmp')
118
+
119
+ #if self.db_installed_version < 6:
120
+ # print 'Updating clients table (v6)'
121
+ # cursor.execute('...')
122
+
123
+ # Updates complete, set the version
124
+ cursor.execute("UPDATE system SET value=%s WHERE name=%s",
125
+ (self.db_version, self.db_version_key))
126
+ db.commit()
127
+ db.close()
128
+ except Exception, e:
129
+ self.log.error("WorklogPlugin Exception: %s" % (e,));
130
+ db.rollback()
131
+
132
+ def do_reports_upgrade(self):
133
+ mgr = CustomReportManager(self.env, self.log)
134
+ r = __import__('reports', globals(), locals(), ['reports'])
135
+
136
+ for report_group in r.reports:
137
+ rlist = report_group['reports']
138
+ group_title = report_group['title']
139
+ for report in rlist:
140
+ title = report['title']
141
+ new_version = report['version']
142
+ mgr.add_report(report["title"], 'Clients Plugin', \
143
+ report['description'], report['sql'], \
144
+ report['uuid'], report['version'],
145
+ 'Timing and Estimation Plugin',
146
+ group_title)
147
+
148
+ def ticket_fields_need_upgrade(self):
149
+ section = 'ticket-custom'
150
+ return ('text' != self.config.get(section, 'client') \
151
+ or 'text' != self.config.get(section, 'clientrate'))
152
+
153
+ def do_ticket_field_upgrade(self):
154
+ section = 'ticket-custom'
155
+
156
+ self.config.set(section,'client', 'text')
157
+ self.config.set(section,'client.label', 'Client')
158
+
159
+ self.config.set(section,'clientrate', 'text')
160
+ self.config.set(section,'clientrate.label', 'Client Charge Rate')
161
+
162
+ self.config.save();
163
+
164
+
165
+ # IEnvironmentSetupParticipant methods
166
+ def environment_created(self):
167
+ """Called when a new Trac environment is created."""
168
+ if self.environment_needs_upgrade(None):
169
+ self.upgrade_environment(None)
170
+
171
+ def environment_needs_upgrade(self, db):
172
+ """Called when Trac checks whether the environment needs to be upgraded.
173
+
174
+ Should return `True` if this participant needs an upgrade to be
175
+ performed, `False` otherwise.
176
+
177
+ """
178
+ return (self.system_needs_upgrade() \
179
+ or self.ticket_fields_need_upgrade())
180
+
181
+ def upgrade_environment(self, db):
182
+ """Actually perform an environment upgrade.
183
+
184
+ Implementations of this method should not commit any database
185
+ transactions. This is done implicitly after all participants have
186
+ performed the upgrades they need without an error being raised.
187
+ """
188
+ print 'ClientsPlugin needs an upgrade'
189
+ print ' * Upgrading db'
190
+ self.do_db_upgrade()
191
+ print ' * Upgrading reports'
192
+ self.do_reports_upgrade()
193
+
194
+ if self.ticket_fields_need_upgrade():
195
+ print ' * Upgrading fields'
196
+ self.do_ticket_field_upgrade()
197
+
198
+ print 'Done Upgrading'
199
+
@@ -0,0 +1,105 @@
1
+ from trac.core import *
2
+ from trac.web.chrome import add_stylesheet, ITemplateProvider
3
+ from trac.web.api import IRequestFilter, ITemplateStreamFilter
4
+ from trac.ticket.api import ITicketManipulator
5
+ from trac.ticket.model import Ticket
6
+ from trac.util.html import html, Markup
7
+
8
+ from genshi.core import Markup
9
+ from genshi.builder import tag
10
+ from genshi.filters.transform import Transformer
11
+
12
+ from clients import model
13
+ from StringIO import StringIO
14
+
15
+ class ClientModule(Component):
16
+ """Allows for tickets to be assigned to particular clients."""
17
+
18
+ implements(IRequestFilter, ITemplateStreamFilter, ITemplateProvider)
19
+
20
+ # IRequestFilter methods
21
+ def pre_process_request(self, req, handler):
22
+ # Add Stylesheet here, so that the ticket page gets it too :)
23
+ add_stylesheet(req, "clients/clients.css")
24
+ return handler
25
+
26
+ def post_process_request(self, req, template, data, content_type):
27
+ # Some ticket views do not actually load the data in this way
28
+ # e.g. action=history or action=diff
29
+ if not data or not data.has_key('fields'):
30
+ return template, data, content_type
31
+
32
+ newticket = req.path_info.startswith('/newticket')
33
+ if req.path_info.startswith('/ticket/') or newticket:
34
+ for field in data['fields']:
35
+ if 'client' == field['name']:
36
+ field['type'] = 'select'
37
+ field['options'] = []
38
+ for client in model.Client.select(self.env):
39
+ field['options'].append(client.name)
40
+ if newticket:
41
+ default_client = self.config.get('ticket', 'default_client')
42
+ if default_client:
43
+ data['ticket']['client'] = default_client
44
+ break;
45
+ elif req.path_info.startswith('/query'):
46
+ if data['fields'].has_key('client'):
47
+ data['fields']['client']['type'] = 'select'
48
+ data['fields']['client']['options'] = []
49
+ for client in model.Client.select(self.env):
50
+ data['fields']['client']['options'].append(client.name)
51
+ return template, data, content_type
52
+
53
+ # ITemplateStreamFilter methods
54
+ def filter_stream(self, req, method, filename, stream, data):
55
+ newticket = req.path_info.startswith('/newticket')
56
+ if req.path_info.startswith('/ticket/') or newticket:
57
+ setdefaultrate = ''
58
+ if newticket:
59
+ setdefaultrate = "$('#field-client').trigger('change');"
60
+
61
+ script = StringIO()
62
+ script.write("""
63
+ $(document).ready(function() {
64
+ $('#field-client').change(function() {
65
+ """);
66
+ script.write('var clientrates = new Array();')
67
+ for client in model.Client.select(self.env):
68
+ script.write('clientrates["%s"] = %s;' % (client.name, client.default_rate or '""'))
69
+ script.write("""
70
+ try { $('#field-clientrate').attr('value', clientrates[this.options[this.selectedIndex].value]); }
71
+ catch (er) { }
72
+ });
73
+ %s
74
+ });""" % setdefaultrate);
75
+ stream |= Transformer('.//head').append(tag.script(script.getvalue(), type_='text/javascript'))
76
+ return stream
77
+
78
+ # ITemplateProvider
79
+ def get_htdocs_dirs(self):
80
+ """Return the absolute path of a directory containing additional
81
+ static resources (such as images, style sheets, etc).
82
+ """
83
+ from pkg_resources import resource_filename
84
+ return [('clients', resource_filename(__name__, 'htdocs'))]
85
+
86
+ def get_templates_dirs(self):
87
+ """Return the absolute path of the directory containing the provided
88
+ ClearSilver templates.
89
+ """
90
+ from pkg_resources import resource_filename
91
+ return [resource_filename(__name__, 'templates')]
92
+
93
+ # ITicketManipulator methods
94
+ def prepare_ticket(self, req, ticket, fields, actions):
95
+ pass
96
+
97
+ def validate_ticket(self, req, ticket):
98
+ # Todo validate client is valid
99
+ pass
100
+ #if req.args.get('action') == 'resolve':
101
+ # links = TicketLinks(self.env, ticket)
102
+ # for i in links.blocked_by:
103
+ # if Ticket(self.env, i)['status'] != 'closed':
104
+ # yield None, 'Ticket #%s is blocking this ticket'%i
105
+
@@ -0,0 +1,287 @@
1
+ import md5
2
+ import sys
3
+ import time
4
+
5
+ from trac.core import *
6
+
7
+ from clients.summary import IClientSummaryProvider
8
+ from clients.action import IClientActionProvider
9
+
10
+
11
+
12
+ __all__ = ['ClientEvent']
13
+
14
+ def simplify_whitespace(name):
15
+ """Strip spaces and remove duplicate spaces within names"""
16
+ return ' '.join(name.split())
17
+
18
+ class ClientEventsSystem(Component):
19
+ summaries = ExtensionPoint(IClientSummaryProvider)
20
+ actions = ExtensionPoint(IClientActionProvider)
21
+
22
+ def get_summaries(self):
23
+ for summary in self.summaries:
24
+ yield summary.get_name()
25
+
26
+ def get_summary(self, name):
27
+ for summary in self.summaries:
28
+ if name == summary.get_name():
29
+ return summary
30
+ return None
31
+
32
+ def get_actions(self):
33
+ for action in self.actions:
34
+ yield action.get_name()
35
+
36
+ def get_action(self, name):
37
+ for action in self.actions:
38
+ if name == action.get_name():
39
+ return action
40
+ return None
41
+
42
+ class ClientEvent(object):
43
+
44
+ def __init__(self, env, name=None, client=None, db=None):
45
+ self.env = env
46
+ if name:
47
+ name = simplify_whitespace(name)
48
+ if name:
49
+ if not db:
50
+ db = self.env.get_db_cnx()
51
+ cursor = db.cursor()
52
+ cursor.execute("SELECT summary, action, lastrun "
53
+ "FROM client_events "
54
+ "WHERE name=%s", (name,))
55
+ row = cursor.fetchone()
56
+ if not row:
57
+ raise TracError('Client Event %s does not exist.' % name)
58
+ self.md5 = md5.new(name).hexdigest()
59
+ self.name = self._old_name = name
60
+ self.summary = row[0] or ''
61
+ self.action = row[1] or ''
62
+ self.lastrun = row[2] or 0
63
+ self._load_options(client, db)
64
+ else:
65
+ self.name = self._old_name = None
66
+ self.summary = ''
67
+ self.action = ''
68
+ self.lastrun = 0
69
+
70
+
71
+ exists = property(fget=lambda self: self._old_name is not None)
72
+
73
+
74
+ def delete(self, db=None):
75
+ assert self.exists, 'Cannot deleting non-existent client event'
76
+ if not db:
77
+ db = self.env.get_db_cnx()
78
+ handle_ta = True
79
+ else:
80
+ handle_ta = False
81
+
82
+ cursor = db.cursor()
83
+ self.env.log.info('Deleting client event %s' % self.name)
84
+ cursor.execute("DELETE FROM client_events WHERE name=%s", (self.name,))
85
+
86
+ self.name = self._old_name = None
87
+
88
+ if handle_ta:
89
+ db.commit()
90
+
91
+ def insert(self, db=None):
92
+ assert not self.exists, 'Cannot insert existing client event'
93
+ system = ClientEventsSystem(self.env);
94
+ assert system.get_summary(self.summary) is not None , 'Invalid summary'
95
+ assert system.get_action(self.action) is not None, 'Invalid action'
96
+ self.name = simplify_whitespace(self.name)
97
+ assert self.name, 'Cannot create client event with no name'
98
+ if not db:
99
+ db = self.env.get_db_cnx()
100
+ handle_ta = True
101
+ else:
102
+ handle_ta = False
103
+
104
+ # Todo: verify client_event with that name does not currently exist...
105
+ cursor = db.cursor()
106
+ self.env.log.debug("Creating new client event '%s'" % self.name)
107
+ cursor.execute("INSERT INTO client_events (name,summary,action,lastrun) "
108
+ "VALUES (%s,%s,%s,%s)",
109
+ (self.name, self.summary, self.action, int(time.time())))
110
+
111
+ if handle_ta:
112
+ db.commit()
113
+
114
+ def _load_client_options(self, client, opttype, db):
115
+ assert self.exists, 'Cannot update non-existent client event'
116
+ assert opttype in ('summary', 'action'), 'Invalid options type'
117
+ system = ClientEventsSystem(self.env);
118
+ if 'summary' == opttype:
119
+ thing = system.get_summary(self.summary)
120
+ assert thing is not None , 'Invalid summary'
121
+ self.summary_description = thing.get_description()
122
+ else:
123
+ thing = system.get_action(self.action)
124
+ assert thing is not None, 'Invalid action'
125
+ self.action_description = thing.get_description()
126
+
127
+ options = {}
128
+ table = 'client_event_' + opttype + '_options'
129
+ cursor = db.cursor()
130
+ cursor.execute("SELECT name, value "
131
+ "FROM " + table + " "
132
+ "WHERE client_event=%s AND client=%s",
133
+ (self._old_name, client or ''))
134
+ for name, value in cursor:
135
+ options[name] = value
136
+ rv = {}
137
+ for option in thing.options(client):
138
+ option['md5'] = md5.new(option['name']).hexdigest()
139
+ if options.has_key(option['name']):
140
+ option['value'] = options[option['name']]
141
+ else:
142
+ option['value'] = ''
143
+ rv[option['name']] = option
144
+ return rv
145
+
146
+
147
+ def _load_options(self, client, db):
148
+ self.summary_options = self._load_client_options(None, 'summary', db)
149
+ self.action_options = self._load_client_options(None, 'action', db)
150
+ if client:
151
+ self.summary_client_options = self._load_client_options(client, 'summary', db)
152
+ self.action_client_options = self._load_client_options(client, 'action', db)
153
+
154
+
155
+ def _update_client_options(self, client, opttype, options, db=None):
156
+ assert self.exists, 'Cannot update non-existent client event'
157
+ assert opttype in ('summary', 'action'), 'Invalid options type'
158
+ system = ClientEventsSystem(self.env);
159
+ if 'summary' == opttype:
160
+ thing = system.get_summary(self.summary)
161
+ assert thing is not None , 'Invalid summary'
162
+ else:
163
+ thing = system.get_action(self.action)
164
+ assert thing is not None, 'Invalid action'
165
+
166
+ if not db:
167
+ db = self.env.get_db_cnx()
168
+ handle_ta = True
169
+ else:
170
+ handle_ta = False
171
+
172
+ table = 'client_event_' + opttype + '_options'
173
+ cursor = db.cursor()
174
+ self.env.log.info('Updating client event "%s"' % self._old_name)
175
+ cursor.execute("DELETE FROM " + table + " "
176
+ "WHERE client_event=%s AND client=%s",
177
+ (self._old_name, client or ''))
178
+
179
+ valid_options = []
180
+ for option in thing.options(client):
181
+ valid_options.append(option['name'])
182
+ for option in options.values():
183
+ if option['name'] in valid_options:
184
+ cursor.execute("INSERT INTO " + table + " (client_event, client, name, value) "
185
+ "VALUES (%s, %s, %s, %s)",
186
+ (self._old_name, client or '', option['name'], option['value']))
187
+
188
+ if handle_ta:
189
+ db.commit()
190
+
191
+
192
+ def update_options(self, client=None, db=None):
193
+ assert self.exists, 'Cannot update non-existent client event'
194
+ if not db:
195
+ db = self.env.get_db_cnx()
196
+ handle_ta = True
197
+ else:
198
+ handle_ta = False
199
+
200
+ if client:
201
+ self._update_client_options(client, 'summary', self.summary_client_options, db)
202
+ self._update_client_options(client, 'action', self.action_client_options, db)
203
+ else:
204
+ self._update_client_options(None, 'summary', self.summary_options, db)
205
+ self._update_client_options(None, 'action', self.action_options, db)
206
+
207
+ if handle_ta:
208
+ db.commit()
209
+
210
+
211
+ def update(self, db=None):
212
+ assert self.exists, 'Cannot update non-existent client event'
213
+ self.name = simplify_whitespace(self.name)
214
+ assert self.name, 'Cannot update client event with no name'
215
+ if not db:
216
+ db = self.env.get_db_cnx()
217
+ handle_ta = True
218
+ else:
219
+ handle_ta = False
220
+
221
+ cursor = db.cursor()
222
+ self.env.log.info('Updating client event "%s"' % self._old_name)
223
+ cursor.execute("UPDATE client_events SET lastrun=%s "
224
+ "WHERE name=%s",
225
+ (int(self.lastrun), self._old_name))
226
+
227
+ if handle_ta:
228
+ db.commit()
229
+
230
+ def trigger(self, req, client, fromdate, todate, db=None):
231
+ assert self.exists, 'Cannot trigger a client event that does not exits'
232
+ system = ClientEventsSystem(self.env);
233
+ summary = system.get_summary(self.summary)
234
+ assert summary is not None , 'Invalid summary'
235
+ action = system.get_action(self.action)
236
+ assert action is not None, 'Invalid action'
237
+
238
+ if not summary.init(self, client):
239
+ self.env.log.info("Could not init summary")
240
+ return False
241
+ if not action.init(self, client):
242
+ self.env.log.info("Could not init action")
243
+ return False
244
+
245
+ self.env.log.debug("Performing action")
246
+ return action.perform(req, summary.get_summary(req, fromdate, todate))
247
+
248
+
249
+ def select(cls, env, client=None, db=None):
250
+ if not db:
251
+ db = env.get_db_cnx()
252
+ cursor = db.cursor()
253
+ cursor.execute("SELECT name, summary, action, lastrun "
254
+ "FROM client_events "
255
+ "ORDER BY name")
256
+ for name, summary, action, lastrun in cursor:
257
+ clev = cls(env)
258
+ clev.md5 = md5.new(name).hexdigest()
259
+ clev.name = clev._old_name = name
260
+ clev.summary = summary or ''
261
+ clev.action = action or ''
262
+ clev.lastrun = lastrun or 0
263
+ clev._load_options(client, db)
264
+ yield clev
265
+ select = classmethod(select)
266
+
267
+ def triggerall(cls, env, req, event, db=None):
268
+ if not db:
269
+ db = env.get_db_cnx()
270
+
271
+ try:
272
+ ev = cls(env, event, None, db)
273
+ except:
274
+ env.log.error("Could not run the event %s" % (event,))
275
+ return
276
+ #ev.lastrun = 1
277
+ now = int(time.time())
278
+
279
+ cursor = db.cursor()
280
+ cursor.execute("SELECT name FROM client ORDER BY name")
281
+ for client, in cursor:
282
+ env.log.info("Running event for client: %s" % (client, ))
283
+ clev = cls(env, event, client)
284
+ clev.trigger(req, client, ev.lastrun, now)
285
+ ev.lastrun = now
286
+ ev.update()
287
+ triggerall = classmethod(triggerall)