Wiki

Case Status Kiln
Register Log In

Wiki

 
Backup FogBugz
  • RSS Feed

Last modified on 8/27/2012 4:31 PM by User.

Tags:

Backup FogBugz

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:

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>