Want to see a quick video demo of how this script works? Check out the video in our blog post about this script.
This script will backup a subset of your FogBugz case data to a set of HTML files that look something like:
This requires two different files:
Table of Contents
fbBackup.py
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: |
''' fbBackup This script backs up the basic information about your FogBugz cases to a set of HTML files (or wherever you want). If you would prefer to save the data to a database, modify saveCase accordingly. To run this script: 1. Because this uses the 'show:unread' search axis, you're going to want to run this script as a dedicated API user. This way, the user can query for unread cases when things have updated. 2. You're going to need Jinja2 installed to use the HTML template. Google how to install it. If you can't get it installed, you'll need to modify saveCase and roll your own method. 3. Edit the CONSTANTS at the top of the file. Note especially the ENCODE_TYPE variable. It's set as ascii because that's foolproof with Jinja2 templates, but if you're storing data in the database, change this to 'UTF-8' 4. Script the code to run and execute like so: > python fbBackup.py Be gentle with your FogBugz server. Don't run this script constantly. However, you want to run it often enough such that you don't build up a huge backlog of unread bugs that you can never get caught up. 5. You can serve the entire directory using Python from the command line: > python -m SimpleHTTPServer Or set it up as a virtual directory in IIS or something. Your call. See https://developers.fogbugz.com/default.asp?W194 for more information on using the FogBugz XML API with Python. ''' BACKUP_DIR = "C:\\Temp\\FBBack" # Where do you want the files to go? TEMPLATE_FILE = "fbBackupTemplate.html" MAX_BUGS = 100 ENCODE_TYPE = 'ascii' #if you're storing data in a database, change this to 'UTF-8' #TODO: Add error handling #TODO: Figure out why Jinja is having issues decoding BugEvent as UTF-8. #TODO: Find a better way to serve the files. (Who am I kidding? This is what FogBugz is for :-) from fogbugz import FogBugz import fbSettings import sys from jinja2 import Template def main(): fb = FogBugz(fbSettings.URL, fbSettings.TOKEN) #Call FogBugz to get all unread cases. This will be anything that's been updated. #We don't want to pull back all data about cases here. That would be huge. resp = fb.search(q='show:"unread" type:"Cases"', cols='ixBug', max=MAX_BUGS) tmpl = Template(open(TEMPLATE_FILE,'r').read()) for case in resp.cases.findAll('case'): ixBug = int(case['ixbug']) #Call FogBugz and get a ton of data about this case (not everything, but it's a lot) respBug = fb.search(q='%s' % ixBug, cols ='sTitle,sPersonAssignedTo,sProject,sArea,sCategory,sPriority,events') xmlBug = respBug.cases.findAll('case')[0] bug = parseCase(xmlBug) saveCase(bug, tmpl) #mark as viewed fb.view(ixBug=ixBug) def parseCase(xmlBug): bug = {} bug['ixBug'] = int(xmlBug['ixbug']) bug['sTitle'] = xmlBug.stitle.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.stitle.string else '' bug['sPersonAssignedTo'] = xmlBug.spersonassignedto.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.spersonassignedto.string else '' bug['sProject'] = xmlBug.sproject.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.sproject.string else '' bug['sArea'] = xmlBug.sarea.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.sarea.string else '' bug['sCategory'] = xmlBug.scategory.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.scategory.string else '' bug['sPriority'] = xmlBug.spriority.string.encode(ENCODE_TYPE, 'ignore') if xmlBug.spriority.string else '' bug['events'] = [] for event in xmlBug.events.findAll('event'): bugEvent = {} bugEvent['ixBugEvent'] = int(event['ixbugevent']) bugEvent['sVerb'] = event.sverb.string.encode(ENCODE_TYPE, 'ignore') if event.sverb.string else '' bugEvent['dt'] = event.dt.string.encode(ENCODE_TYPE, 'ignore') if event.dt.string else '' bugEvent['s'] = event.s.string.encode(ENCODE_TYPE, 'ignore') if event.s.string else '' bugEvent['sChanges'] = event.schanges.string.encode(ENCODE_TYPE, 'ignore') if event.schanges.string else '' bugEvent['evtDescription'] = event.evtdescription.string.encode(ENCODE_TYPE, 'ignore') if event.evtdescription.string else '' bugEvent['sPerson'] = event.sperson.string.encode(ENCODE_TYPE, 'ignore') if event.sperson.string else '' bugEvent['s'] = event.s.string.encode(ENCODE_TYPE,'ignore') if event.s.string else '' bug['events'].append(bugEvent) return bug def saveCase(bug, template): ''' There's probably (read: definitely) a better way to save your case data, but this isn't a bad start. It simply outputs the bug info to an html file with minimal formatting. Alternatively, replace this code and save the data to a database. ''' f = open('%s\\%s.html' % (BACKUP_DIR,bug['ixBug']),'w') f.write(template.render(bug=bug)) f.close() main() |
fbBackupTemplate.html
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: |
<!DOCTYPE html> <html lang="en"> <head> <title>{{bug.ixBug|e}} | {{bug.sTitle|e}}</title> <style type="text/css"> .bugHeader { outline-width: 2px; outline-style: solid; width: 800px; padding: 5px; } .bugEvent { outline-width: 2px; outline-style: solid; outline-color: blue; width: 800px; padding: 5px; } pre.s { white-space: pre-wrap; } </style> </head> <body> <div class="bugHeader"> <span class="ixBug">Bug ID: {{bug.ixBug|e}}</span><br> <span class="sTitle">Title: {{bug.sTitle|e}}</span><br> <span class="sPersonAssignedTo">Assigned To: {{bug.sPersonAssignedTo|e}}</span><br> <span class="sProject">Project: {{bug.sProject|e}}</span><br> <span class="sArea">Area: {{bug.sArea|e}}</span><br> <span class="sCategory">Category: {{bug.sCategory|e}}</span><br> <span class="sPriority">Title: {{bug.sPriority|e}}</span><br> </div> <div class="bugEvents"> {% for event in bug.events %} <div class="bugEvent"> <span class="ixBugEvent">BugEvent ID: {{event.ixBugEvent|e}}</span><br> <span class="dt">Date/Time: {{event.dt|e}}</span><br> <span class="sVerb">Verb: {{event.sVerb|e}}</span><br> <span class="evtDescription">Event Description: {{event.evtDescription|e}}</span><br> <span class="sChanges">Changes: {{event.sChanges|e}}</span><br> <span class="sPerson">Person: {{event.sPerson|e}}</span><br> <span class="Text">Text: </span><br> <pre class="s">{{event.s|e}}</pre><br> </div> {% endfor %} </div> </body> </html> |