keithsalisbury-subtrac 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION.yml +1 -1
- data/bin/subtrac +2 -0
- data/lib/subtrac.rb +85 -50
- data/lib/subtrac/config/config.yml +22 -19
- data/lib/subtrac/templates/location.erb +2 -2
- data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +1 -1
- data/lib/subtrac/templates/trac.erb +1 -1
- data/lib/subtrac/templates/vhost.erb +6 -6
- metadata +1 -105
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +0 -419
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +0 -3
- data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +0 -20
- data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +0 -28
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +0 -168
- data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +0 -137
- data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +0 -91
- data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +0 -199
- data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +0 -105
- data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +0 -287
- data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +0 -71
- data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +0 -4
- data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +0 -135
- data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +0 -70
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +0 -142
- data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +0 -231
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +0 -27
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +0 -152
- data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +0 -160
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +0 -124
- data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +0 -134
- data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +0 -132
- data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +0 -97
- data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +0 -161
- data/lib/subtrac/trac-plugins/clientsplugin/setup.py +0 -43
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +0 -4
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +0 -273
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +0 -44
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +0 -36
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +0 -32
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +0 -409
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +0 -30
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +0 -53
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +0 -181
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +0 -66
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +0 -47
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +0 -93
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +0 -86
- data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +0 -20
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +0 -23
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +0 -128
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +0 -40
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +0 -285
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +0 -173
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
- data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +0 -164
- data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +0 -69
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +0 -1
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +0 -292
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +0 -172
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +0 -178
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +0 -25
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +0 -6
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +0 -356
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +0 -100
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +0 -84
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +0 -1149
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +0 -119
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +0 -73
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +0 -165
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +0 -28
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +0 -221
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +0 -675
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +0 -150
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +0 -25
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +0 -131
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +0 -84
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +0 -104
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +0 -194
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +0 -62
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +0 -28
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +0 -127
- data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +0 -129
- data/lib/subtrac/trac-plugins/worklogplugin/setup.py +0 -29
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +0 -1
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +0 -187
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +0 -40
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +0 -67
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +0 -12
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +0 -7
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +0 -40
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +0 -208
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +0 -1439
- 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 +0 -80
- 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 +0 -336
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +0 -598
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +0 -45
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +0 -70
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +0 -40
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +0 -59
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +0 -33
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +0 -153
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +0 -96
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +0 -29
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +0 -31
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +0 -47
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +0 -174
- data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +0 -73
@@ -1,173 +0,0 @@
|
|
1
|
-
from datetime import datetime as dt
|
2
|
-
import time
|
3
|
-
from utils import mail
|
4
|
-
import adw_tracdb as db
|
5
|
-
|
6
|
-
#defaultUrl= "https://sekhemt.acceleration.net/ADW/"
|
7
|
-
_defaultUrl= "https://10.10.10.219/projects"
|
8
|
-
_htmlLocation = '/var/BigVisibleCharts/Billing'
|
9
|
-
|
10
|
-
def cond ( boolExpr, trueResult, falseResult ):
|
11
|
-
""" This is the classic ?: operator from languages like C expressed in python (from dive into python)
|
12
|
-
"""
|
13
|
-
return (boolExpr and [trueResult] or [falseResult])[0]
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def accumulator():
|
18
|
-
var = [0]
|
19
|
-
def fn(*params):
|
20
|
-
for param in params:
|
21
|
-
var[0] = var[0] + param
|
22
|
-
return var[0]
|
23
|
-
return fn
|
24
|
-
|
25
|
-
def progn(*params):
|
26
|
-
return params[-1]
|
27
|
-
|
28
|
-
def prinTrue(s):
|
29
|
-
print s
|
30
|
-
return True
|
31
|
-
|
32
|
-
|
33
|
-
def wn (name, attribs, *children):
|
34
|
-
return "<%s %s>\n%s\n</%s>\n" % \
|
35
|
-
(name, \
|
36
|
-
' '.join(['%s="%s"' % (key, val) for (key, val) in attribs.items()]), \
|
37
|
-
'\n'.join([str(c) for c in children]), \
|
38
|
-
name)
|
39
|
-
|
40
|
-
def ticket_link (number, projUrl):
|
41
|
-
return wn('a' , {'href':'/'.join([projUrl,'ticket' ,str(number)])}, "#"+str(number))
|
42
|
-
|
43
|
-
def milestone_link (name, projUrl):
|
44
|
-
name = str(name)
|
45
|
-
return wn('a' , {'href':'/'.join([projUrl,'milestone' ,name])}, name)
|
46
|
-
|
47
|
-
def make_project_output(project, rs, totalAcc):
|
48
|
-
projAcc = accumulator()
|
49
|
-
projLink = '/'.join([_defaultUrl, project ])
|
50
|
-
|
51
|
-
def make_cell(idx, val):
|
52
|
-
if(idx == rs.columnMap['ticket']):
|
53
|
-
val = ticket_link(val, projLink)
|
54
|
-
elif idx == rs.columnMap['milestone'] and val != ' ':
|
55
|
-
val = milestone_link(val, projLink)
|
56
|
-
elif idx == rs.columnMap['hours']:
|
57
|
-
projAcc(float(val))
|
58
|
-
totalAcc(float(val))
|
59
|
-
return wn('td', {}, val)
|
60
|
-
|
61
|
-
return progn(wn('div', {},
|
62
|
-
wn('h2', {},
|
63
|
-
wn('a',{'href':projLink},project)),
|
64
|
-
wn('table', {"cellspacing":"0", "border":"1", "cellpadding":"3"},
|
65
|
-
wn('tr', {},
|
66
|
-
*[wn('th',{}, name) for name in rs.columnNames]),
|
67
|
-
*[wn('tr', {},
|
68
|
-
*[make_cell(idx, val)
|
69
|
-
for idx in range(0, len(row))
|
70
|
-
for val in [row[idx]]])
|
71
|
-
for row in rs.rows]),
|
72
|
-
wn('span',{}, "Total: "+str(projAcc()))))
|
73
|
-
|
74
|
-
def make_all_projects_output():
|
75
|
-
totalAcc = accumulator()
|
76
|
-
sql = """
|
77
|
-
SELECT
|
78
|
-
CASE WHEN t.milestone IS NOT NULL and t.milestone <> '' THEN t.milestone
|
79
|
-
ELSE ' '
|
80
|
-
END as milestone,
|
81
|
-
t.id as ticket,
|
82
|
-
SUM(newvalue) as hours,
|
83
|
-
t.summary as summary,
|
84
|
-
strftime('%m/%d/%Y %H:%M:%S', MAX(ticket_change.time), 'unixepoch', 'localtime') as [most-recent-update] ,
|
85
|
-
(SELECT CASE WHEN MAX(time) IS NOT NULL THEN strftime('%m/%d/%Y %H:%M:%S', MAX(time), 'unixepoch', 'localtime')
|
86
|
-
ELSE 'No previous bill date'
|
87
|
-
END as time FROM bill_date ) as [previous-bill-date]
|
88
|
-
FROM ticket as t
|
89
|
-
LEFT JOIN ticket_custom as billable on billable.ticket = t.id
|
90
|
-
AND billable.name = 'billable'
|
91
|
-
JOIN ticket_change on t.id = ticket_change.ticket
|
92
|
-
AND (
|
93
|
-
ticket_change.time >
|
94
|
-
(SELECT CASE WHEN MAX(time) IS NOT NULL THEN MAX(time)
|
95
|
-
ELSE 0
|
96
|
-
END as time FROM bill_date )
|
97
|
-
)
|
98
|
-
WHERE ticket_change.field = 'hours'
|
99
|
-
AND billable.value=1
|
100
|
-
GROUP BY t.milestone, t.id
|
101
|
-
"""
|
102
|
-
|
103
|
-
billingInfo = db.collectResultsFromAllTracs(sql);
|
104
|
-
projects_output = '\n'.join([make_project_output(project, rs, totalAcc)
|
105
|
-
for (project, rs) in billingInfo
|
106
|
-
if rs.rows ])
|
107
|
-
return wn('html', {},
|
108
|
-
wn('head', {}),
|
109
|
-
wn('body', {},
|
110
|
-
projects_output,
|
111
|
-
wn('span',{},"Total hours billed: "+str(totalAcc()))))
|
112
|
-
|
113
|
-
|
114
|
-
def save_output_to_file(output, when=0):
|
115
|
-
if not when:
|
116
|
-
when = dt.now()
|
117
|
-
fname = '_'.join(["billing", str(when.year),
|
118
|
-
str(when.month), str(when.day),
|
119
|
-
str(when.hour), str(when.minute), ".html"])
|
120
|
-
p = "/".join([_htmlLocation, fname])
|
121
|
-
print "----"
|
122
|
-
print "Writing out billing information to '%s'" % p
|
123
|
-
print "----"
|
124
|
-
f = open(p, "w")
|
125
|
-
f.write(output)
|
126
|
-
f.close();
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
def run_billing(emails="ryan@acceleration.net", when=0):
|
131
|
-
if not when:
|
132
|
-
when = dt.now()
|
133
|
-
date = '/'.join([str(when.month), str(when.day), str(when.year)])
|
134
|
-
|
135
|
-
print "Collecting output..."
|
136
|
-
output = make_all_projects_output()
|
137
|
-
save_output_to_file(output, when)
|
138
|
-
print "Emailing results to %s" % emails
|
139
|
-
if emails:
|
140
|
-
mail.mail(emails, 'Trac Billing - %s ' % date, output, html=True, fromEmail='trac-tickets@acceleration.net')
|
141
|
-
return output
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
def add_bill_date(project, username="Timing and Estimation Plugin", when=0):
|
146
|
-
now = time.time()
|
147
|
-
if not when:
|
148
|
-
when = now
|
149
|
-
when = int(when)
|
150
|
-
now = int(now)
|
151
|
-
sql = """
|
152
|
-
INSERT INTO bill_date (time, set_when, str_value)
|
153
|
-
VALUES (?, ?, strftime('%m/%d/%Y %H:%M:%S',?, 'unixepoch', 'localtime'))
|
154
|
-
"""
|
155
|
-
db.executeNonQuery(project, sql, when, now, when)
|
156
|
-
|
157
|
-
def mark_billing_date_in_all_projects(when=0 ):
|
158
|
-
print "Marking the bill date on all projects."
|
159
|
-
if not when:
|
160
|
-
when = time.time()
|
161
|
-
for project in db.projects:
|
162
|
-
try:
|
163
|
-
add_bill_date(project, "Timing and Estimation Plugin", when);
|
164
|
-
print "%s Succeeded." % project
|
165
|
-
except Exception, e:
|
166
|
-
print "* %s failed: %s" % (project , e.args)
|
167
|
-
print "Done marking bill dates"
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
File without changes
|
@@ -1,164 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Helper library to ease emailing.
|
3
|
-
"""
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
import smtplib
|
8
|
-
import email
|
9
|
-
from email import Encoders
|
10
|
-
from email.Utils import formatdate
|
11
|
-
from email.Message import Message
|
12
|
-
from email.MIMEAudio import MIMEAudio
|
13
|
-
from email.MIMEBase import MIMEBase
|
14
|
-
from email.MIMEMultipart import MIMEMultipart
|
15
|
-
from email.MIMEImage import MIMEImage
|
16
|
-
from email.MIMEText import MIMEText
|
17
|
-
import mimetypes
|
18
|
-
|
19
|
-
FROMEMAIL = 'ryan@acceleration.net'
|
20
|
-
SERVER = 'mail.acceleration.net'
|
21
|
-
|
22
|
-
def rawEmail(toList, msg, server=SERVER, fromEmail=FROMEMAIL):
|
23
|
-
|
24
|
-
s = smtplib.SMTP(server)
|
25
|
-
smtpresult = s.sendmail(fromEmail, toList, msg)
|
26
|
-
|
27
|
-
if smtpresult:
|
28
|
-
errstr = ""
|
29
|
-
for recip in smtpresult.keys():
|
30
|
-
errstr = """Could not delivery mail to: %s
|
31
|
-
|
32
|
-
Server said: %s
|
33
|
-
%s
|
34
|
-
|
35
|
-
%s""" % (recip, smtpresult[recip][0], smtpresult[recip][1], errstr)
|
36
|
-
raise smtplib.SMTPException, errstr
|
37
|
-
|
38
|
-
|
39
|
-
def processTo(to):
|
40
|
-
"""
|
41
|
-
helper function that processes a string or list of addresses.
|
42
|
-
|
43
|
-
returns (list of addresses, comma-delimited string of addresses)
|
44
|
-
|
45
|
-
The first is used for rawEmail, and the second is used when creating
|
46
|
-
headers.
|
47
|
-
"""
|
48
|
-
toList = []
|
49
|
-
if type(to) is list:
|
50
|
-
toList = to[:]
|
51
|
-
to = ", ".join(to)
|
52
|
-
else:
|
53
|
-
toList = [to]
|
54
|
-
|
55
|
-
return (toList, to)
|
56
|
-
|
57
|
-
def mail(to, subject, message, html=False, fromEmail=FROMEMAIL, server=SERVER):
|
58
|
-
"""
|
59
|
-
Simplifies the emailing process, sending plaintext emails.
|
60
|
-
|
61
|
-
to: accepts a list of emails or a single email.
|
62
|
-
|
63
|
-
returns nothing, or throws an smtplib.SMTPException if there is a problem.
|
64
|
-
"""
|
65
|
-
toList, to = processTo(to)
|
66
|
-
|
67
|
-
htmlHeader = '\n'
|
68
|
-
|
69
|
-
if html:
|
70
|
-
htmlHeader = 'Content-Type: text/html; charset=ISO-8859-1\n\n'
|
71
|
-
|
72
|
-
msg = '''To: %s
|
73
|
-
From: %s
|
74
|
-
Subject: %s
|
75
|
-
Date: %s
|
76
|
-
%s
|
77
|
-
%s
|
78
|
-
''' % (to, fromEmail, subject, formatdate(), htmlHeader, message)
|
79
|
-
|
80
|
-
rawEmail(toList, msg, fromEmail=fromEmail, server=server)
|
81
|
-
|
82
|
-
|
83
|
-
def sms_me(subject, message):
|
84
|
-
" sends an email to ryan's phone "
|
85
|
-
mail('sms-ryan@acceleration.net', subject, message)
|
86
|
-
|
87
|
-
def emailHtml(to, subject, message):
|
88
|
-
toList, to = processTo(to)
|
89
|
-
msg = MIMEMultipart()
|
90
|
-
msg['Subject'] = subject
|
91
|
-
msg['To'] = to
|
92
|
-
msg['From'] = fromEmail
|
93
|
-
msg['Date'] = formatdate()
|
94
|
-
msg.preamble = 'You are not using a MIME-aware reader\n'
|
95
|
-
msg.epilogue = ''
|
96
|
-
|
97
|
-
#add the main message
|
98
|
-
msg.attach(MIMEText(message))
|
99
|
-
|
100
|
-
rawEmail(toList, msg.as_string())
|
101
|
-
|
102
|
-
def emailFile(to, subject, message, filePath, mimeType=None, fromEmail=FROMEMAIL, server=SERVER):
|
103
|
-
"""
|
104
|
-
sends an email attachment to the given address or list of addesses.
|
105
|
-
|
106
|
-
if the mimeType is not specified, it uses mimetypes.guess_type to determine it.
|
107
|
-
"""
|
108
|
-
toList, to = processTo(to)
|
109
|
-
|
110
|
-
msg = MIMEMultipart()
|
111
|
-
msg['Subject'] = subject
|
112
|
-
msg['To'] = to
|
113
|
-
msg['From'] = fromEmail
|
114
|
-
msg['Date'] = formatdate()
|
115
|
-
msg.preamble = 'You are not using a MIME-aware reader\n'
|
116
|
-
msg.epilogue = ''
|
117
|
-
|
118
|
-
#add the main message
|
119
|
-
msg.attach(MIMEText(message))
|
120
|
-
|
121
|
-
if type(filePath) is list:
|
122
|
-
for f in filePath:
|
123
|
-
addFile(f, msg, mimeType)
|
124
|
-
else:
|
125
|
-
addFile(filePath, msg, mimeType)
|
126
|
-
|
127
|
-
rawEmail(toList, msg.as_string(), server=server, fromEmail=fromEmail)
|
128
|
-
|
129
|
-
def addFile(filePath, message, mimeType=None):
|
130
|
-
if mimeType is None:
|
131
|
-
mimeType = mimetypes.guess_type(filePath)[0]
|
132
|
-
|
133
|
-
maintype, subtype = mimeType.split('/', 1)
|
134
|
-
|
135
|
-
def processFactory(mimeRunner, openMethod):
|
136
|
-
def proc(path, subtype):
|
137
|
-
fp = open(path, openMethod)
|
138
|
-
fileMsg = mimeRunner(fp.read(), _subtype = subtype)
|
139
|
-
fp.close()
|
140
|
-
return fileMsg
|
141
|
-
|
142
|
-
return proc
|
143
|
-
|
144
|
-
messageMaker = {'text':processFactory(MIMEText, 'r'),
|
145
|
-
'image':processFactory(MIMEImage, 'rb'),
|
146
|
-
'audio':processFactory(MIMEAudio, 'rb')}
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
fMsg = None
|
151
|
-
if messageMaker.has_key(maintype):
|
152
|
-
fMsg = messageMaker[maintype](filePath, subtype)
|
153
|
-
else:
|
154
|
-
fp = open(filePath, 'rb')
|
155
|
-
fMsg = MIMEBase(maintype, subtype)
|
156
|
-
fMsg.set_payload(fp.read())
|
157
|
-
fp.close()
|
158
|
-
# Encode the payload using Base64
|
159
|
-
Encoders.encode_base64(fMsg)
|
160
|
-
|
161
|
-
if fMsg is not None:
|
162
|
-
fMsg.add_header('Content-Disposition', 'attachment', filename=filePath)
|
163
|
-
message.attach(fMsg)
|
164
|
-
|
@@ -1,69 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
|
3
|
-
from setuptools import setup
|
4
|
-
|
5
|
-
PACKAGE = 'timingandestimationplugin'
|
6
|
-
|
7
|
-
setup(name=PACKAGE,
|
8
|
-
description='Plugin to make Trac support time estimation and tracking with permissions',
|
9
|
-
keywords='trac plugin estimation timetracking permissions',
|
10
|
-
version='0.7.8',
|
11
|
-
url='http://www.trac-hacks.org/wiki/TimingAndEstimationPlugin',
|
12
|
-
license='http://www.opensource.org/licenses/mit-license.php',
|
13
|
-
author='Russ Tyndall at Acceleration.net',
|
14
|
-
author_email='russ@acceleration.net',
|
15
|
-
long_description="""
|
16
|
-
This Trac 0.11 plugin provides support for Time estimation and tracking,
|
17
|
-
and permissions to view and set those fields
|
18
|
-
|
19
|
-
See http://trac-hacks.org/wiki/TimingAndEstimationPlugin for details.
|
20
|
-
""",
|
21
|
-
packages=[PACKAGE],
|
22
|
-
package_data={PACKAGE : ['templates/*.html', 'htdocs/js/*', 'htdocs/*.css', 'htdocs/*.js']},
|
23
|
-
entry_points={'trac.plugins': '%s = %s' % (PACKAGE, PACKAGE)})
|
24
|
-
|
25
|
-
|
26
|
-
#### FINANCIAL CONTRIBUTERS ####
|
27
|
-
#
|
28
|
-
# Obsidian Software: http://www.obsidiansoft.com/
|
29
|
-
# Enterprise Solutions for Functional Processor
|
30
|
-
# Design Verification
|
31
|
-
#
|
32
|
-
################################
|
33
|
-
|
34
|
-
#### AUTHORS ####
|
35
|
-
## Primary Author:
|
36
|
-
## Russell Tyndall
|
37
|
-
## Acceleration.net
|
38
|
-
## russ@acceleration.net
|
39
|
-
## trac-hacks user: bobbysmith007
|
40
|
-
|
41
|
-
##
|
42
|
-
|
43
|
-
## Alessio Massaro
|
44
|
-
## trac-hacks user: masariello
|
45
|
-
## Helped Get Reports working in postgres
|
46
|
-
## and started moving toward generic work
|
47
|
-
## rather than hours
|
48
|
-
|
49
|
-
## kkurzweil@lulu.com
|
50
|
-
## helped postegresql db backend compatiblity
|
51
|
-
|
52
|
-
## jonas
|
53
|
-
## made it so that base_url was unnecessary
|
54
|
-
|
55
|
-
## Colin Guthrie
|
56
|
-
## trac-hacks user: coling
|
57
|
-
## Refactored the custom reports code to make it
|
58
|
-
## easy for other plugins to provide reports to
|
59
|
-
## compliment those provided by default
|
60
|
-
## Added Javascript that improves Ticket UI
|
61
|
-
|
62
|
-
## Dave Abrahams <dave@boost-consulting.com>
|
63
|
-
##
|
64
|
-
## Genshi filters to remove T&E reports from the
|
65
|
-
## standard reports page, where they display errors
|
66
|
-
|
67
|
-
## Greg Troxel
|
68
|
-
##
|
69
|
-
## Updated the post commit hooks to be inline with upstream trac
|
data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
from api import *
|
@@ -1,292 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
import dbhelper
|
3
|
-
import time
|
4
|
-
from tande_filters import *
|
5
|
-
from reports_filter import *
|
6
|
-
from blackmagic import *
|
7
|
-
from ticket_daemon import *
|
8
|
-
from ticket_webui import *
|
9
|
-
from usermanual import *
|
10
|
-
from ticket_policy import *
|
11
|
-
from trac.log import logger_factory
|
12
|
-
from trac.ticket import ITicketChangeListener, Ticket
|
13
|
-
from trac.core import *
|
14
|
-
from trac.env import IEnvironmentSetupParticipant
|
15
|
-
from trac.perm import IPermissionRequestor, PermissionSystem
|
16
|
-
from webui import *
|
17
|
-
from query_webui import *
|
18
|
-
from reportmanager import CustomReportManager
|
19
|
-
from statuses import *
|
20
|
-
from reports import all_reports
|
21
|
-
from sets import Set
|
22
|
-
|
23
|
-
## report columns
|
24
|
-
## id|author|title|query|description
|
25
|
-
|
26
|
-
class TimeTrackingSetupParticipant(Component):
|
27
|
-
""" This is the config that must be there for this plugin to work:
|
28
|
-
|
29
|
-
[ticket-custom]
|
30
|
-
totalhours = text
|
31
|
-
totalhours.value = 0
|
32
|
-
totalhours.label = Total Hours
|
33
|
-
|
34
|
-
billable = checkbox
|
35
|
-
billable.value = 1
|
36
|
-
billable.label = Is this billable?
|
37
|
-
|
38
|
-
hours = text
|
39
|
-
hours.value = 0
|
40
|
-
hours.label = Hours to Add
|
41
|
-
|
42
|
-
estimatedhours = text
|
43
|
-
estimatedhours.value = 0
|
44
|
-
estimatedhours.label = Estimated Hours?
|
45
|
-
|
46
|
-
internal = checkbox
|
47
|
-
internal.value = 0
|
48
|
-
internal.label = Internal?
|
49
|
-
|
50
|
-
"""
|
51
|
-
implements(IEnvironmentSetupParticipant)
|
52
|
-
db_version_key = None
|
53
|
-
db_version = None
|
54
|
-
db_installed_version = None
|
55
|
-
|
56
|
-
"""Extension point interface for components that need to participate in the
|
57
|
-
creation and upgrading of Trac environments, for example to create
|
58
|
-
additional database tables."""
|
59
|
-
def __init__(self):
|
60
|
-
# Setup logging
|
61
|
-
self.statuses_key = 'T&E-statuses'
|
62
|
-
self.db_version_key = 'TimingAndEstimationPlugin_Db_Version'
|
63
|
-
self.db_version = 8
|
64
|
-
# Initialise database schema version tracking.
|
65
|
-
self.db_installed_version = dbhelper.get_system_value(self, \
|
66
|
-
self.db_version_key) or 0
|
67
|
-
|
68
|
-
def environment_created(self):
|
69
|
-
"""Called when a new Trac environment is created."""
|
70
|
-
if self.environment_needs_upgrade(None):
|
71
|
-
self.upgrade_environment(None)
|
72
|
-
|
73
|
-
|
74
|
-
def system_needs_upgrade(self):
|
75
|
-
return self.db_installed_version < self.db_version
|
76
|
-
|
77
|
-
def do_db_upgrade(self):
|
78
|
-
if self.db_installed_version < 1:
|
79
|
-
print "Creating bill_date table"
|
80
|
-
sql = """
|
81
|
-
CREATE TABLE bill_date (
|
82
|
-
time integer,
|
83
|
-
set_when integer,
|
84
|
-
str_value text
|
85
|
-
);
|
86
|
-
"""
|
87
|
-
dbhelper.execute_non_query(self, sql)
|
88
|
-
|
89
|
-
|
90
|
-
if self.db_installed_version < 5:
|
91
|
-
if dbhelper.db_table_exists(self, 'report_version'):
|
92
|
-
print "Dropping report_version table"
|
93
|
-
sql = "DELETE FROM report " \
|
94
|
-
"WHERE author=%s AND id IN (SELECT report FROM report_version)"
|
95
|
-
dbhelper.execute_non_query(self, sql, 'Timing and Estimation Plugin')
|
96
|
-
|
97
|
-
sql = "DROP TABLE report_version"
|
98
|
-
dbhelper.execute_non_query(self, sql)
|
99
|
-
|
100
|
-
#version 6 upgraded reports
|
101
|
-
|
102
|
-
|
103
|
-
if self.db_installed_version < 7:
|
104
|
-
field_settings = "field settings"
|
105
|
-
self.config.set( field_settings, "fields", "billable, totalhours, hours, estimatedhours, internal" )
|
106
|
-
self.config.set( field_settings, "billable.permission", "TIME_VIEW:hide, TIME_RECORD:disable" )
|
107
|
-
self.config.set( field_settings, "hours.permission", "TIME_VIEW:remove, TIME_RECORD:disable" )
|
108
|
-
self.config.set( field_settings, "estimatedhours.permission", "TIME_RECORD:disable" )
|
109
|
-
self.config.set( field_settings, "internal.permission", "TIME_RECORD:hide")
|
110
|
-
|
111
|
-
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
112
|
-
# This statement block always goes at the end this method
|
113
|
-
dbhelper.set_system_value(self, self.db_version_key, self.db_version)
|
114
|
-
self.db_installed_version = self.db_version
|
115
|
-
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
def reports_need_upgrade(self):
|
120
|
-
mgr = CustomReportManager(self.env, self.log)
|
121
|
-
db_reports = mgr.get_version_hash_by_group(CustomReportManager.TimingAndEstimationKey)
|
122
|
-
py_reports = {}
|
123
|
-
for report_group in all_reports:
|
124
|
-
for report in report_group['reports']:
|
125
|
-
py_reports[report['uuid']]= report['version']
|
126
|
-
|
127
|
-
diff = [(uuid, version) for (uuid, version) in py_reports.items()
|
128
|
-
if not db_reports.has_key(uuid) or int(db_reports[uuid]) < int(version)]
|
129
|
-
|
130
|
-
if len(diff) > 0:
|
131
|
-
self.log.debug ("T&E needs upgrades for the following reports: %s" %
|
132
|
-
(diff, ))
|
133
|
-
return len(diff) > 0
|
134
|
-
|
135
|
-
def do_reports_upgrade(self, force=False):
|
136
|
-
self.log.debug( "Beginning Reports Upgrade");
|
137
|
-
mgr = CustomReportManager(self.env, self.log)
|
138
|
-
statuses = get_statuses(self)
|
139
|
-
stat_vars = status_variables(statuses)
|
140
|
-
|
141
|
-
for report_group in all_reports:
|
142
|
-
rlist = report_group["reports"]
|
143
|
-
group_title = report_group["title"]
|
144
|
-
for report in rlist:
|
145
|
-
title = report["title"]
|
146
|
-
new_version = report["version"]
|
147
|
-
|
148
|
-
sql = report["sql"].replace('#STATUSES#', stat_vars)
|
149
|
-
mgr.add_report(report["title"], "Timing and Estimation Plugin", \
|
150
|
-
"Reports Must Be Accessed From the Management Screen",
|
151
|
-
sql, report["uuid"], report["version"],
|
152
|
-
CustomReportManager.TimingAndEstimationKey,
|
153
|
-
group_title, force)
|
154
|
-
|
155
|
-
def ticket_fields_need_upgrade(self):
|
156
|
-
ticket_custom = "ticket-custom"
|
157
|
-
return not ( self.config.get( ticket_custom, "totalhours" ) and \
|
158
|
-
self.config.get( ticket_custom, "hours" ) and \
|
159
|
-
self.config.get( ticket_custom, "totalhours.order") and \
|
160
|
-
self.config.get( ticket_custom, "hours.order") and \
|
161
|
-
self.config.get( ticket_custom, "estimatedhours.order") and \
|
162
|
-
self.config.get( ticket_custom, "estimatedhours") and \
|
163
|
-
self.config.get( ticket_custom, "internal") and \
|
164
|
-
"InternalTicketsPolicy" in self.config.getlist("trac", "permission_policies"))
|
165
|
-
|
166
|
-
def do_ticket_field_upgrade(self):
|
167
|
-
ticket_custom = "ticket-custom"
|
168
|
-
|
169
|
-
if not self.config.get(ticket_custom,"totalhours"):
|
170
|
-
self.config.set(ticket_custom,"totalhours", "text")
|
171
|
-
self.config.set(ticket_custom,"totalhours.order", "4")
|
172
|
-
self.config.set(ticket_custom,"totalhours.value", "0")
|
173
|
-
self.config.set(ticket_custom,"totalhours.label", "Total Hours")
|
174
|
-
|
175
|
-
|
176
|
-
if not self.config.get(ticket_custom,"billable"):
|
177
|
-
self.config.set(ticket_custom,"billable", "checkbox")
|
178
|
-
self.config.set(ticket_custom,"billable.value", "1")
|
179
|
-
self.config.set(ticket_custom,"billable.order", "3")
|
180
|
-
self.config.set(ticket_custom,"billable.label", "Billable?")
|
181
|
-
|
182
|
-
if not self.config.get(ticket_custom,"hours"):
|
183
|
-
self.config.set(ticket_custom,"hours", "text")
|
184
|
-
self.config.set(ticket_custom,"hours.value", "0")
|
185
|
-
self.config.set(ticket_custom,"hours.order", "2")
|
186
|
-
self.config.set(ticket_custom,"hours.label", "Add Hours to Ticket")
|
187
|
-
|
188
|
-
if not self.config.get(ticket_custom,"estimatedhours"):
|
189
|
-
self.config.set(ticket_custom,"estimatedhours", "text")
|
190
|
-
self.config.set(ticket_custom,"estimatedhours.value", "0")
|
191
|
-
self.config.set(ticket_custom,"estimatedhours.order", "1")
|
192
|
-
self.config.set(ticket_custom,"estimatedhours.label", "Estimated Number of Hours")
|
193
|
-
|
194
|
-
if not self.config.get( ticket_custom, "internal"):
|
195
|
-
self.config.set(ticket_custom, "internal", "checkbox")
|
196
|
-
self.config.set(ticket_custom, "internal.value", "0")
|
197
|
-
self.config.set(ticket_custom, "internal.label", "Internal?")
|
198
|
-
self.config.set(ticket_custom,"internal.order", "5")
|
199
|
-
|
200
|
-
if "InternalTicketsPolicy" not in self.config.getlist("trac", "permission_policies"):
|
201
|
-
perms = ["InternalTicketsPolicy"]
|
202
|
-
other_policies = self.config.getlist("trac", "permission_policies")
|
203
|
-
if "DefaultPermissionPolicy" not in other_policies:
|
204
|
-
perms.append("DefaultPermissionPolicy")
|
205
|
-
perms.extend( other_policies )
|
206
|
-
self.config.set("trac", "permission_policies", ', '.join(perms))
|
207
|
-
|
208
|
-
self.config.save();
|
209
|
-
|
210
|
-
def needs_user_man(self):
|
211
|
-
maxversion = dbhelper.get_scalar(self, "SELECT MAX(version) FROM wiki WHERE name like %s", 0,
|
212
|
-
user_manual_wiki_title)
|
213
|
-
if (not maxversion) or maxversion < user_manual_version:
|
214
|
-
return True
|
215
|
-
return False
|
216
|
-
|
217
|
-
def do_user_man_update(self):
|
218
|
-
|
219
|
-
when = int(time.time())
|
220
|
-
sql = """
|
221
|
-
INSERT INTO wiki (name,version,time,author,ipnr,text,comment,readonly)
|
222
|
-
VALUES ( %s, %s, %s, 'Timing and Estimation Plugin', '127.0.0.1', %s,'',0)
|
223
|
-
"""
|
224
|
-
dbhelper.execute_non_query(self, sql,
|
225
|
-
user_manual_wiki_title,
|
226
|
-
user_manual_version,
|
227
|
-
when,
|
228
|
-
user_manual_content)
|
229
|
-
|
230
|
-
|
231
|
-
def environment_needs_upgrade(self, db):
|
232
|
-
"""Called when Trac checks whether the environment needs to be upgraded.
|
233
|
-
|
234
|
-
Should return `True` if this participant needs an upgrade to be
|
235
|
-
performed, `False` otherwise.
|
236
|
-
|
237
|
-
"""
|
238
|
-
self.log.debug("NEEDS UP?: sys:%s, rep:%s, stats:%s, fields:%s, man:%s" % \
|
239
|
-
((self.system_needs_upgrade()),
|
240
|
-
(self.reports_need_upgrade()),
|
241
|
-
(self.have_statuses_changed()),
|
242
|
-
(self.ticket_fields_need_upgrade()),
|
243
|
-
(self.needs_user_man())))
|
244
|
-
return (self.system_needs_upgrade()) or \
|
245
|
-
(self.reports_need_upgrade()) or \
|
246
|
-
(self.have_statuses_changed()) or \
|
247
|
-
(self.ticket_fields_need_upgrade()) or \
|
248
|
-
(self.needs_user_man())
|
249
|
-
|
250
|
-
def upgrade_environment(self, db):
|
251
|
-
"""Actually perform an environment upgrade.
|
252
|
-
|
253
|
-
Implementations of this method should not commit any database
|
254
|
-
transactions. This is done implicitly after all participants have
|
255
|
-
performed the upgrades they need without an error being raised.
|
256
|
-
"""
|
257
|
-
def p(s):
|
258
|
-
print s
|
259
|
-
return True
|
260
|
-
print "Timing and Estimation needs an upgrade"
|
261
|
-
p("Upgrading Database")
|
262
|
-
self.do_db_upgrade()
|
263
|
-
p("Upgrading reports")
|
264
|
-
self.do_reports_upgrade(force=self.have_statuses_changed())
|
265
|
-
|
266
|
-
#make sure we upgrade the statuses string so that we dont need to always rebuild the
|
267
|
-
# reports
|
268
|
-
stats = get_statuses(self)
|
269
|
-
val = ','.join(list(stats))
|
270
|
-
dbhelper.set_system_value(self, self.statuses_key, val)
|
271
|
-
|
272
|
-
if self.ticket_fields_need_upgrade():
|
273
|
-
p("Upgrading fields")
|
274
|
-
self.do_ticket_field_upgrade()
|
275
|
-
if self.needs_user_man():
|
276
|
-
p("Upgrading usermanual")
|
277
|
-
self.do_user_man_update()
|
278
|
-
print "Done Upgrading"
|
279
|
-
|
280
|
-
def have_statuses_changed(self):
|
281
|
-
"""get the statuses from the last time we saved them,
|
282
|
-
compare them to the ones we have now (ignoring '' and None),
|
283
|
-
if we have different ones, throw return true
|
284
|
-
"""
|
285
|
-
s = dbhelper.get_system_value(self, self.statuses_key)
|
286
|
-
if not s:
|
287
|
-
return True
|
288
|
-
sys_stats = get_statuses(self)
|
289
|
-
s = s.split(',')
|
290
|
-
sys_stats.symmetric_difference_update(s)
|
291
|
-
sys_stats.difference_update(['', None])
|
292
|
-
return len(sys_stats) > 0
|