Edgewall Software

TracImport: fogbugz2csv.py

File fogbugz2csv.py, 6.8 KB (added by patriciomolina@…, 3 years ago)

Exports Fogbugz's tickets in CSV format (then you can use it with csv2trac.py)

Line 
1# FogBugz2CSV Migration Tool
2
3import MySQLdb
4import xml.sax.saxutils as saxutils
5
6DB_HOSTNAME = 'localhost' # FogBugz's database hostname (i.e.: 'localhost')
7DB_USERNAME = '' # FogBugz's database username
8DB_PASSWORD = '' # FogBugz's database password
9DB_DATABASE = 'fogbugz' # FogBugz's database
10
11# Using FogBugz's 'category' for conversion
12# In Trac, 'type' must be one of: task, defect, enhancement
13TYPE = {
14    '1': 'defect', # Bug in FogBugz, Defect in Trac
15    '2': 'enhancement', # Feature in FogBugz, Enhancement in Trac
16    '3': 'task', # Inquiry in FogBugz, Task in Trac
17    '4': 'task', # Schedule Item in FogBugz, Task in Trac
18}
19
20# FogBugz's 'priority' for conversion
21# In Trac, 'severity' must be one of: blocker, critical, major, normal, minor, trivial, enhancement
22SEVERITY = {
23    '1': 'blocker', # Blocker in FogBugz, Blocker in Trac
24    '2': 'critical', # Critical in FogBugz, Critical in Trac
25    '3': 'major', # Major in FogBugz, Major in Trac
26    '4': 'normal', # Normal in FogBugz, Normal in Trac
27    '5': 'minor', # Minor in FogBugz, Minor in Trac
28    '6': 'trivial', # Trivial in FogBugz, Trivial in Trac
29    '7': 'enhancement', # Enhancement in FogBugz, Enhancement in Trac
30}
31
32# Not in FogBugz, using 'priority' for conversion
33# In Trac, 'priority' must be one of: highest, high, normal, low, lowest
34PRIORITY = {
35    '1': 'highest', # Blocker in FogBugz, Highest in Trac
36    '2': 'high', # Critical in FogBugz, High in Trac
37    '3': 'high', # Major in FogBugz, High in Trac
38    '4': 'normal', # Normal in FogBugz, Normal in Trac
39    '5': 'low', # Minor in FogBugz, Low in Trac
40    '6': 'low', # Trivial in FogBugz, Low in Trac
41    '7': 'lowest', # Enhancement in FogBugz, Lowest in Trac
42}
43
44# Using Fogbugz's 'status' for conversion
45# In Trac, 'status' must be one of: new, assigned, reopened, closed
46STATUS = {
47    '1': 'assigned', # Active in FogBugz, Assigned in Trac
48    '2': 'closed', # Resolved (Fixed) in FogBugz, Closed in Trac
49    '3': 'closed', # Resolved (Not Reproducible) in FogBugz, Closed in Trac
50    '4': 'closed', # Resolved (Duplicate) in FogBugz, Closed in Trac
51    '5': 'closed', # Resolved (Postponed) in FogBugz, Closed in Trac
52    '6': 'closed', # Resolved (Won't Fix) in FogBugz, Closed in Trac
53    '7': 'closed', # Resolved (By Design) in FogBugz, Closed in Trac
54    '8': 'closed', # Resolved (Implemented) in FogBugz, Closed in Trac
55    '9': 'closed', # Resolved (Won't Impement) in FogBugz, Closed in Trac
56    '10': 'closed', # Resolved (Already Exists) in FogBugz, Closed in Trac
57    '11': 'closed', # Resolved (Responded) in FogBugz, Closed in Trac
58    '12': 'closed', # Resolved (Won't Respond) in FogBugz, Closed in Trac
59    '13': 'closed', # Resolved (SPAM) in FogBugz, Closed in Trac
60    '14': 'closed', # Resolved (Waiting For Info) in FogBugz, Closed in Trac
61    '15': 'closed', # Resolved (Completed) in FogBugz, Closed in Trac
62    '16': 'closed', # Resolved (Canceled) in FogBugz, Closed in Trac
63}
64
65# Each line in the CSV file needs to have the following entries
66# [M] type        text           -- the ticket purpose
67# time            integer        -- the time it was created
68# changetime      integer
69# component       text
70# [M] severity    text
71# [M] priority    text
72# owner           text           -- who is this ticket assigned to
73# reporter        text
74# cc              text           -- email addresses to notify
75# url             text           -- url related to this ticket
76# version         text           --
77# milestone       text           --
78# [M] status      text
79# resolution      text
80# summary         text           -- one-line summary
81# description     text           -- problem description (long)
82# keywords        text     
83
84def format_value(value):
85    string = str(value)
86    string = string.replace("\\'", "'")
87    string = string.replace("'", "''")
88    string = string.replace("\r", "")
89    string = string.replace("\n", "\\n")
90    string = string.replace('"', '""')
91    string = string.replace(' ', '')
92    string = string.replace('"', '""')
93    return '"' + saxutils.unescape(string) + '"'
94
95def get_username(full_name): 
96    """
97    Returns Trac's username
98    """ 
99    names = full_name.lower().split(' ')
100    if len(names) > 1:
101        # First letter from the first name and complete surname
102        # i.e.: John Doe will be converted to 'jdoe'
103        return names[0][0] + names[1]
104    else:
105        return full_name
106
107def main():
108    db = MySQLdb.connect(
109        host=DB_HOSTNAME,
110        user=DB_USERNAME,
111        passwd=DB_PASSWORD,
112        db=DB_DATABASE
113    )
114    cursor = db.cursor()
115    # Getting data from FogBugz's database
116    sql = """
117    SELECT
118    Bug.ixCategory AS type,
119    UNIX_TIMESTAMP(Bug.dtOpened) AS time,
120    UNIX_TIMESTAMP(NOW()) AS changetime,
121    Project.sProject AS component,
122    Bug.ixPriority AS severity,
123    Bug.ixPriority AS priority,
124    Owner.sFullName AS owner,
125    Reporter.sFullName AS reporter,
126    '' AS cc,
127    '' AS url,
128    Bug.sVersion AS version,
129    FixFor.sFixFor AS milestone,
130    Bug.ixStatus as status,
131    '' AS resolution,
132    Bug.sTitle AS summary,
133    (SELECT s FROM BugEvent WHERE BugEvent.ixBug = Bug.ixBug ORDER BY BugEvent.ixBugEvent ASC LIMIT 1) AS description,
134    '' AS keywords
135    FROM Bug
136    LEFT JOIN Project ON Project.ixProject = Bug.ixProject
137    LEFT JOIN Person AS Owner ON Owner.ixPerson = Bug.ixPersonAssignedTo
138    LEFT JOIN Person AS Reporter ON Reporter.ixPerson = Bug.ixPersonOpenedBy
139    LEFT JOIN FixFor ON FixFor.ixFixFor = Bug.ixFixFor
140    LEFT JOIN BugEvent ON Bug.ixBugEventLatestText = BugEvent.ixBugEvent
141    ORDER BY Bug.ixBug ASC;
142    """
143    cursor.execute(sql)
144    result = cursor.fetchall()
145    # CSV header
146    print 'type,time,changetime,component,severity,priority,owner,reporter,cc,url,version,milestone,status,resolution,summary,description,keywords'
147    for row in result:
148        export = (
149            TYPE.get(str(row[0])), # type, converting FogBugz's 'category' to Trac's 'type'
150            str(row[1]), # time
151            str(row[2]), # changetime
152            str(row[3]), # component
153            SEVERITY.get(str(row[4])), # severity, converting FogBugz's 'priority' to Trac's 'severity'
154            PRIORITY.get(str(row[5])), # priority, converting FogBugz's 'priority' to Trac's 'priority'
155            get_username(str(row[6])), # owner
156            get_username(str(row[7])), # reporter
157            str(row[8]), # cc
158            str(row[9]), # url
159            str(row[10]), # version
160            str(row[11]), # milestone
161            STATUS.get(str(row[12])), # status, converting FogBugz's 'status' to Trac's 'status'
162            str(row[13]), # resolution
163            format_value(str(row[14])), # summary (escaped)
164            format_value(str(row[15])), # description (escaped)
165            format_value(str(row[16])), # keywords
166        )
167        print ','.join(export) # Don't like csv module, sorry
168
169if __name__ == '__main__':
170    main()