keithsalisbury-subtrac 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +56 -0
- data/VERSION.yml +4 -0
- data/bin/subtrac +40 -0
- data/lib/subtrac.rb +245 -0
- data/lib/subtrac/common/clients/index.wsgi +89 -0
- data/lib/subtrac/common/favicon.ico +0 -0
- data/lib/subtrac/common/images/trac/banner_bg.jpg +0 -0
- data/lib/subtrac/common/images/trac/bar_bg.gif +0 -0
- data/lib/subtrac/common/images/trac/footer_back.png +0 -0
- data/lib/subtrac/common/images/trac/main_bg.gif +0 -0
- data/lib/subtrac/common/images/trac/saint_logo_small.png +0 -0
- data/lib/subtrac/common/static/404.html +14 -0
- data/lib/subtrac/common/styles/trac.css +222 -0
- data/lib/subtrac/common/trac.ini +178 -0
- data/lib/subtrac/config/config.yml +54 -0
- data/lib/subtrac/passwords +1 -0
- data/lib/subtrac/shared/trac.ini +178 -0
- data/lib/subtrac/templates/location.erb +16 -0
- data/lib/subtrac/templates/projects/blank/svn/branches/README +0 -0
- data/lib/subtrac/templates/projects/blank/svn/tags/README +0 -0
- data/lib/subtrac/templates/projects/blank/svn/trunk/README +0 -0
- data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +57 -0
- data/lib/subtrac/templates/projects/new/svn/trunk/trac/wiki/WikiStart +46 -0
- data/lib/subtrac/templates/projects/new/trac/wiki/WikiStart +23 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/index/index.html +22 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/layout.html +56 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/site.html +27 -0
- data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/theme.html +86 -0
- data/lib/subtrac/templates/projects/trac_theme/trac/wiki/WikiStart +4 -0
- data/lib/subtrac/templates/trac.erb +25 -0
- data/lib/subtrac/templates/vhost.erb +35 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +419 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +3 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +20 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +28 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +168 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +137 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +91 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +199 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +105 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +287 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +71 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +4 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +135 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +70 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +142 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +231 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +27 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +152 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +160 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +124 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +134 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +132 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +97 -0
- data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +161 -0
- data/lib/subtrac/trac-plugins/clientsplugin/setup.py +43 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +4 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +273 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +44 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +36 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +32 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +409 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +30 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +53 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +181 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +66 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +47 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +93 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +86 -0
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +20 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +23 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +128 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +40 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +285 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +173 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +164 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +69 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +1 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +292 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +172 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +178 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +25 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +6 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +356 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +100 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +84 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +1149 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +119 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +73 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +165 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +28 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +221 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +675 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +150 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +25 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +131 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +84 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +104 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +194 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +62 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +28 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +127 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +129 -0
- data/lib/subtrac/trac-plugins/worklogplugin/setup.py +29 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +1 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +187 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +67 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +12 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +7 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +208 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +1439 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.xcf +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/worklogplugin.css +80 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstart.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstop.png +0 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/manager.py +336 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +598 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +45 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +70 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +40 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +59 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +33 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +153 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +96 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +29 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +31 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +47 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +174 -0
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +73 -0
- data/lib/subtrac/version.rb +4 -0
- metadata +191 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
from setuptools import setup, find_packages
|
4
|
+
|
5
|
+
setup(
|
6
|
+
name='AdvancedTicketWorkflowPlugin',
|
7
|
+
version='0.10',
|
8
|
+
author = 'Eli Carter',
|
9
|
+
author_email = 'elicarter@retracile.net',
|
10
|
+
license='BSD',
|
11
|
+
description = 'Advanced workflow operations Trac plugin',
|
12
|
+
long_description = 'Provides more advanced workflow operations',
|
13
|
+
url = 'http://trac-hacks.org/wiki/AdvancedTicketWorkflowPlugin',
|
14
|
+
|
15
|
+
packages = find_packages(),
|
16
|
+
package_data = {},
|
17
|
+
entry_points = {'trac.plugins':['advancedworkflow.controller = advancedworkflow.controller']},
|
18
|
+
install_requires = [],
|
19
|
+
#zip_safe = False,
|
20
|
+
)
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from lxml import etree
|
2
|
+
from trac.core import *
|
3
|
+
|
4
|
+
class IClientActionProvider(Interface):
|
5
|
+
"""Extension point interface for components that define their own way
|
6
|
+
to act on a given client summary (IClientSummaryProvider.get_summary
|
7
|
+
"""
|
8
|
+
|
9
|
+
def get_name():
|
10
|
+
"""Return the name of the action (for use in UI)
|
11
|
+
"""
|
12
|
+
|
13
|
+
def get_description():
|
14
|
+
"""Return the description of the action (for use in UI)
|
15
|
+
"""
|
16
|
+
|
17
|
+
def options(client=None):
|
18
|
+
"""The options for this action
|
19
|
+
If the client option is None then these options are defined as being per instance.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def init(event, client):
|
23
|
+
"""Todo
|
24
|
+
"""
|
25
|
+
|
26
|
+
def perform(req, summary):
|
27
|
+
"""Perform the action. This must return an etree object
|
28
|
+
"""
|
@@ -0,0 +1,168 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import locale
|
5
|
+
import time
|
6
|
+
import codecs
|
7
|
+
from datetime import datetime
|
8
|
+
from StringIO import StringIO
|
9
|
+
|
10
|
+
from trac import __version__
|
11
|
+
from trac.core import *
|
12
|
+
from trac.env import open_environment
|
13
|
+
from trac.util.datefmt import format_date, to_datetime
|
14
|
+
from trac.wiki import wiki_to_html
|
15
|
+
from genshi import escape
|
16
|
+
|
17
|
+
from lxml import etree
|
18
|
+
from clients.action import IClientActionProvider
|
19
|
+
|
20
|
+
|
21
|
+
class ClientActionEmail(Component):
|
22
|
+
implements(IClientActionProvider)
|
23
|
+
|
24
|
+
client = None
|
25
|
+
debug = False
|
26
|
+
|
27
|
+
def get_name(self):
|
28
|
+
return "Send Email"
|
29
|
+
|
30
|
+
def get_description(self):
|
31
|
+
return "Send an email to a certain list of addresses"
|
32
|
+
|
33
|
+
def options(self, client=None):
|
34
|
+
if client is None:
|
35
|
+
yield {'name': 'XSLT', 'description': 'Formatting XSLT to convert the summary to an email', 'type': 'large'}
|
36
|
+
yield {'name': 'Subject', 'description': 'Email subject (use %s to replace the active client name)', 'type': 'medium'}
|
37
|
+
else:
|
38
|
+
yield {'name': 'Email Addresses', 'description': 'Comma separated list of email addresses', 'type': 'medium'}
|
39
|
+
|
40
|
+
|
41
|
+
def init(self, event, client):
|
42
|
+
self.client = client
|
43
|
+
if not event.action_options.has_key('XSLT') or not event.action_options['XSLT']['value']:
|
44
|
+
return False
|
45
|
+
try:
|
46
|
+
self.transform = etree.XSLT(etree.fromstring(str(event.action_options['XSLT']['value'])))
|
47
|
+
except:
|
48
|
+
self.env.log.error("Error: Cannot load/parse stylesheet")
|
49
|
+
return False
|
50
|
+
|
51
|
+
if not event.action_client_options.has_key('Email Addresses') or not event.action_client_options['Email Addresses']['value']:
|
52
|
+
return False
|
53
|
+
|
54
|
+
self.emails = []
|
55
|
+
for email in event.action_client_options['Email Addresses']['value'].replace(',', ' ').split(' '):
|
56
|
+
if '' != email.strip():
|
57
|
+
self.emails.append(email.strip())
|
58
|
+
|
59
|
+
if not self.emails:
|
60
|
+
return False
|
61
|
+
|
62
|
+
if not event.action_options.has_key('Subject') or not event.action_options['Subject']['value']:
|
63
|
+
self.subject = 'Ticket Summary for %s'
|
64
|
+
else:
|
65
|
+
self.subject = event.action_options['Subject']['value']
|
66
|
+
|
67
|
+
if self.subject.find('%s') >= 0:
|
68
|
+
self.subject = self.subject % (client,)
|
69
|
+
|
70
|
+
return True
|
71
|
+
|
72
|
+
|
73
|
+
def perform(self, req, summary):
|
74
|
+
if summary is None:
|
75
|
+
return False
|
76
|
+
self.config = self.env.config
|
77
|
+
self.encoding = 'utf-8'
|
78
|
+
subject = self.subject
|
79
|
+
|
80
|
+
if not self.config.getbool('notification', 'smtp_enabled'):
|
81
|
+
return False
|
82
|
+
smtp_server = self.config['notification'].get('smtp_server')
|
83
|
+
smtp_port = self.config['notification'].getint('smtp_port')
|
84
|
+
from_email = self.config['notification'].get('smtp_from')
|
85
|
+
from_name = self.config['notification'].get('smtp_from_name')
|
86
|
+
replyto_email = self.config['notification'].get('smtp_replyto')
|
87
|
+
from_email = from_email or replyto_email
|
88
|
+
if not from_email:
|
89
|
+
return False
|
90
|
+
|
91
|
+
# Authentication info (optional)
|
92
|
+
user_name = self.config['notification'].get('smtp_user')
|
93
|
+
password = self.config['notification'].get('smtp_password')
|
94
|
+
|
95
|
+
# Thanks to the author of this recipe:
|
96
|
+
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473810
|
97
|
+
|
98
|
+
from email.MIMEMultipart import MIMEMultipart
|
99
|
+
from email.MIMEText import MIMEText
|
100
|
+
from email.MIMEImage import MIMEImage
|
101
|
+
from email.Charset import add_charset, SHORTEST
|
102
|
+
add_charset( 'utf-8', SHORTEST, None, None )
|
103
|
+
|
104
|
+
projname = self.config.get('project', 'name')
|
105
|
+
|
106
|
+
# Create the root message and fill in the from, to, and subject headers
|
107
|
+
msg_root = MIMEMultipart('alternative')
|
108
|
+
msg_root['To'] = str(', ').join(self.emails)
|
109
|
+
|
110
|
+
msg_root['X-Mailer'] = 'ClientsPlugin for Trac'
|
111
|
+
msg_root['X-Trac-Version'] = __version__
|
112
|
+
msg_root['X-Trac-Project'] = projname
|
113
|
+
msg_root['Precedence'] = 'bulk'
|
114
|
+
msg_root['Auto-Submitted'] = 'auto-generated'
|
115
|
+
msg_root['Subject'] = subject
|
116
|
+
msg_root['From'] = '%s <%s>' % (from_name or projname, from_email)
|
117
|
+
msg_root['Reply-To'] = replyto_email
|
118
|
+
msg_root.preamble = 'This is a multi-part message in MIME format.'
|
119
|
+
|
120
|
+
view = 'plain'
|
121
|
+
arg = "'%s'" % view
|
122
|
+
result = self.transform(summary, view=arg)
|
123
|
+
msg_text = MIMEText(str(result), view, self.encoding)
|
124
|
+
msg_root.attach(msg_text)
|
125
|
+
|
126
|
+
msg_related = MIMEMultipart('related')
|
127
|
+
msg_root.attach(msg_related)
|
128
|
+
|
129
|
+
view = 'html'
|
130
|
+
arg = "'%s'" % view
|
131
|
+
result = self.transform(summary, view=arg)
|
132
|
+
#file = open('/tmp/send-client-email.html', 'w')
|
133
|
+
#file.write(str(result))
|
134
|
+
#file.close()
|
135
|
+
|
136
|
+
msg_text = MIMEText(str(result), view, self.encoding)
|
137
|
+
msg_related.attach(msg_text)
|
138
|
+
|
139
|
+
# Handle image embedding...
|
140
|
+
view = 'images'
|
141
|
+
arg = "'%s'" % view
|
142
|
+
result = self.transform(summary, view=arg)
|
143
|
+
if result:
|
144
|
+
images = result.getroot()
|
145
|
+
if images:
|
146
|
+
for img in images:
|
147
|
+
if 'img' != img.tag:
|
148
|
+
continue
|
149
|
+
if not img.get('id') or not img.get('src'):
|
150
|
+
continue
|
151
|
+
|
152
|
+
fp = open(img.get('src'), 'rb')
|
153
|
+
if not fp:
|
154
|
+
continue
|
155
|
+
msg_img = MIMEImage(fp.read())
|
156
|
+
fp.close()
|
157
|
+
msg_img.add_header('Content-ID', '<%s>' % img.get('id'))
|
158
|
+
msg_related.attach(msg_img)
|
159
|
+
|
160
|
+
# Send the email
|
161
|
+
import smtplib
|
162
|
+
smtp = smtplib.SMTP() #smtp_server, smtp_port)
|
163
|
+
if False and user_name:
|
164
|
+
smtp.login(user_name, password)
|
165
|
+
smtp.connect()
|
166
|
+
smtp.sendmail(from_email, self.emails, msg_root.as_string())
|
167
|
+
smtp.quit()
|
168
|
+
return True
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import locale
|
5
|
+
import time
|
6
|
+
import codecs
|
7
|
+
import httplib
|
8
|
+
import urlparse
|
9
|
+
from datetime import datetime
|
10
|
+
from StringIO import StringIO
|
11
|
+
|
12
|
+
from trac.core import *
|
13
|
+
from trac.env import open_environment
|
14
|
+
from trac.util.datefmt import format_date, to_datetime
|
15
|
+
from trac.wiki import wiki_to_html
|
16
|
+
from genshi import escape
|
17
|
+
|
18
|
+
from lxml import etree
|
19
|
+
from clients.action import IClientActionProvider
|
20
|
+
|
21
|
+
|
22
|
+
class ClientActionZendesk(Component):
|
23
|
+
implements(IClientActionProvider)
|
24
|
+
|
25
|
+
client = None
|
26
|
+
debug = False
|
27
|
+
|
28
|
+
def get_name(self):
|
29
|
+
return "Post to Zendesk"
|
30
|
+
|
31
|
+
def get_description(self):
|
32
|
+
return "Post the summary to a Zendesk forum topic"
|
33
|
+
|
34
|
+
def options(self, client=None):
|
35
|
+
if client is None:
|
36
|
+
yield {'name': 'XSLT', 'description': 'Formatting XSLT to convert the summary to a Zendesk compatible post', 'type': 'large'}
|
37
|
+
yield {'name': 'Username', 'description': 'Zendesk username', 'type': 'medium'}
|
38
|
+
yield {'name': 'Password', 'description': 'Zendesk password', 'type': 'medium'}
|
39
|
+
yield {'name': 'Method', 'description': 'Interaction Method', 'type': 'list', 'vals': ['POST', 'PUT']}
|
40
|
+
else:
|
41
|
+
yield {'name': 'Zendesk URI', 'description': 'Zendesk Forum REST URI', 'type': 'medium'}
|
42
|
+
|
43
|
+
|
44
|
+
def init(self, event, client):
|
45
|
+
self.client = client
|
46
|
+
if not event.action_options.has_key('XSLT') or not event.action_options['XSLT']['value']:
|
47
|
+
return False
|
48
|
+
try:
|
49
|
+
self.transform = etree.XSLT(etree.fromstring(str(event.action_options['XSLT']['value'])))
|
50
|
+
except:
|
51
|
+
print "Error: Cannot load/parse stylesheet"
|
52
|
+
return False
|
53
|
+
|
54
|
+
if not event.action_options.has_key('Username') or not event.action_options['Username']['value']:
|
55
|
+
return False
|
56
|
+
self.username = event.action_options['Username']['value']
|
57
|
+
|
58
|
+
if not event.action_options.has_key('Password') or not event.action_options['Password']['value']:
|
59
|
+
return False
|
60
|
+
self.password = event.action_options['Password']['value']
|
61
|
+
|
62
|
+
if not event.action_options.has_key('Method') or not event.action_options['Method']['value']:
|
63
|
+
return False
|
64
|
+
self.method = event.action_options['Method']['value']
|
65
|
+
|
66
|
+
if not event.action_client_options.has_key('Zendesk URI') or not event.action_client_options['Zendesk URI']['value']:
|
67
|
+
return False
|
68
|
+
self.uri = event.action_client_options['Zendesk URI']['value']
|
69
|
+
|
70
|
+
return True
|
71
|
+
|
72
|
+
|
73
|
+
def perform(self, req, summary):
|
74
|
+
def parseuri(uri):
|
75
|
+
"""Parse URI, return (host, port, path) tuple.
|
76
|
+
|
77
|
+
>>> parseuri('http://example.org/testing?somequery#frag')
|
78
|
+
('example.org', 80, '/testing?somequery')
|
79
|
+
>>> parseuri('http://example.net:8080/test.html')
|
80
|
+
('example.net', 8080, '/test.html')
|
81
|
+
"""
|
82
|
+
|
83
|
+
scheme, netplace, path, query, fragid = urlparse.urlsplit(uri)
|
84
|
+
|
85
|
+
if ':' in netplace:
|
86
|
+
host, port = netplace.split(':', 2)
|
87
|
+
port = int(port)
|
88
|
+
else: host, port = netplace, 80
|
89
|
+
|
90
|
+
if query: path += '?' + query
|
91
|
+
|
92
|
+
return host, port, path
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
if summary is None:
|
97
|
+
return False
|
98
|
+
|
99
|
+
result = self.transform(summary)
|
100
|
+
|
101
|
+
username = self.username
|
102
|
+
password = self.password
|
103
|
+
uri = self.uri
|
104
|
+
|
105
|
+
host, port, path = parseuri(uri)
|
106
|
+
|
107
|
+
okay = set([200, 201, 204])
|
108
|
+
|
109
|
+
import base64
|
110
|
+
userpass = username + ':' + password
|
111
|
+
userpass = base64.encodestring(userpass).strip()
|
112
|
+
authorization = 'Basic ' + userpass
|
113
|
+
|
114
|
+
# Attempt to HTTP PUT the data
|
115
|
+
h = httplib.HTTPConnection(host, port)
|
116
|
+
|
117
|
+
data = str(result)
|
118
|
+
h.putrequest('PUT', path)
|
119
|
+
|
120
|
+
h.putheader('User-Agent', 'Trac/1.0')
|
121
|
+
h.putheader('Accept', 'application/xml')
|
122
|
+
h.putheader('Content-Type', 'text/xml')
|
123
|
+
h.putheader('Authorization', authorization)
|
124
|
+
h.putheader('Content-Length', len(data))
|
125
|
+
h.endheaders()
|
126
|
+
|
127
|
+
h.send(str(data))
|
128
|
+
|
129
|
+
resp = h.getresponse()
|
130
|
+
status = resp.status # an int
|
131
|
+
|
132
|
+
# Got a response, now decide how to act upon it
|
133
|
+
if status not in okay:
|
134
|
+
print 'Got "%s %s"' % (status, resp.reason)
|
135
|
+
return False
|
136
|
+
|
137
|
+
return True
|
@@ -0,0 +1,91 @@
|
|
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.model import Client
|
9
|
+
from clients.events import ClientEvent
|
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
|
+
class ClientAdminPanel(TicketAdminPanel):
|
15
|
+
|
16
|
+
_type = 'clients'
|
17
|
+
_label = ('Client', 'Clients')
|
18
|
+
|
19
|
+
# TicketAdminPanel methods
|
20
|
+
def _render_admin_panel(self, req, cat, page, client):
|
21
|
+
# Detail view?
|
22
|
+
if client:
|
23
|
+
clnt = Client(self.env, client)
|
24
|
+
events = ClientEvent.select(self.env, client)
|
25
|
+
if req.method == 'POST':
|
26
|
+
if req.args.get('save'):
|
27
|
+
clnt.name = req.args.get('name')
|
28
|
+
clnt.description = req.args.get('description')
|
29
|
+
clnt.changes_list = req.args.get('changes_list')
|
30
|
+
clnt.changes_period = req.args.get('changes_period')
|
31
|
+
clnt.summary_list = req.args.get('summary_list')
|
32
|
+
clnt.summary_period = req.args.get('summary_period')
|
33
|
+
clnt.default_rate = req.args.get('default_rate')
|
34
|
+
clnt.currency = req.args.get('currency')
|
35
|
+
clnt.update()
|
36
|
+
|
37
|
+
db = self.env.get_db_cnx()
|
38
|
+
for clev in events:
|
39
|
+
for option in clev.summary_client_options:
|
40
|
+
arg = 'summary-option-%s-%s' % (clev.md5, clev.summary_client_options[option]['md5'])
|
41
|
+
clev.summary_client_options[option]['value'] = req.args.get(arg)
|
42
|
+
for option in clev.action_client_options:
|
43
|
+
arg = 'action-option-%s-%s' % (clev.md5, clev.action_client_options[option]['md5'])
|
44
|
+
clev.action_client_options[option]['value'] = req.args.get(arg)
|
45
|
+
clev.update_options(client, db)
|
46
|
+
db.commit()
|
47
|
+
|
48
|
+
req.redirect(req.href.admin(cat, page))
|
49
|
+
elif req.args.get('cancel'):
|
50
|
+
req.redirect(req.href.admin(cat, page))
|
51
|
+
|
52
|
+
add_script(req, 'common/js/wikitoolbar.js')
|
53
|
+
data = {'view': 'detail', 'client': clnt, 'events': events}
|
54
|
+
|
55
|
+
else:
|
56
|
+
if req.method == 'POST':
|
57
|
+
# Add Client
|
58
|
+
if req.args.get('add') and req.args.get('name'):
|
59
|
+
clnt = Client(self.env)
|
60
|
+
clnt.name = req.args.get('name')
|
61
|
+
clnt.insert()
|
62
|
+
req.redirect(req.href.admin(cat, page))
|
63
|
+
|
64
|
+
# Remove clients
|
65
|
+
elif req.args.get('remove') and req.args.get('sel'):
|
66
|
+
sel = req.args.get('sel')
|
67
|
+
sel = isinstance(sel, list) and sel or [sel]
|
68
|
+
if not sel:
|
69
|
+
raise TracError('No client selected')
|
70
|
+
db = self.env.get_db_cnx()
|
71
|
+
for name in sel:
|
72
|
+
clnt = Client(self.env, name, db=db)
|
73
|
+
clnt.delete(db=db)
|
74
|
+
db.commit()
|
75
|
+
req.redirect(req.href.admin(cat, page))
|
76
|
+
|
77
|
+
# Set default client
|
78
|
+
elif req.args.get('apply'):
|
79
|
+
if req.args.get('default'):
|
80
|
+
name = req.args.get('default')
|
81
|
+
self.log.info('Setting default client to %s', name)
|
82
|
+
self.config.set('ticket', 'default_client', name)
|
83
|
+
self.config.save()
|
84
|
+
req.redirect(req.href.admin(cat, page))
|
85
|
+
|
86
|
+
default = self.config.get('ticket', 'default_client')
|
87
|
+
data = {'view': 'list',
|
88
|
+
'clients': Client.select(self.env),
|
89
|
+
'default': default}
|
90
|
+
|
91
|
+
return 'admin_clients.html', data
|