fogbugzfogbugz-api

Exporting case details from FogBugz


We are seeking to create a snapshot of all our FogBugz data however the GUI only seems to allow to export the top level list of cases, not their detail/attachments. Searching around the only solutions I can find relate to apps that are now out of date.


Solution

  • I found this very hard to find any help on but eventually I figured out how to write a script to do this and wanted to share it. Please note I'm not a Python programmer nor a HTML/CSS guy so no flaming please!

    It started as a sample script from their help site and I adapted it to include attachments and to be stand alone.

    First download and install Python3. Then pip install fogbugz and jinja2. Place these two files in your python directory, fillin the variables (including the token half way down the script) and then run backit.

    1- backit.py

    from fogbugz import FogBugz
    import sys
    from jinja2 import Template
    import os
    import urllib
    
    def main():
    
      S_FOGBUGZ_URL   = 'https://whatever.fogbugz.com/'
      S_EMAIL         = 'email'
      S_PASSWORD      = 'pass'
      MAX_BUGS        = 9999
    
      TEMPLATE_FILE = 'C:\\Users\\zzz\\AppData\\Local\\Programs\\Python\\Python36\\fbBackupTemplate.html'
    
      fb = FogBugz(S_FOGBUGZ_URL)
      fb.logon(S_EMAIL, S_PASSWORD)
    
      resp = fb.search(q='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'])
          print(ixBug)
    
    
    
          respBug = fb.search(q='%s' % ixBug,cols ='sTitle,sPersonAssignedTo,sProject,sArea,sCategory,sPriority,events')
          xmlBug = respBug.cases.findAll('case')[0]
    
    
    
          bug = {}
          bug['ixBug'] = int(xmlBug['ixBug'])
          bug['sTitle'] = xmlBug.sTitle.string if xmlBug.sTitle.string else ''
          bug['sPersonAssignedTo'] = xmlBug.sPersonAssignedTo.string if xmlBug.sPersonAssignedTo.string else ''
          bug['sProject'] = xmlBug.sProject.string if xmlBug.sProject.string else ''
          bug['sArea'] = xmlBug.sArea.string if xmlBug.sArea.string else ''
          bug['sCategory'] = xmlBug.sCategory.string if xmlBug.sCategory.string else ''
          bug['sPriority'] = xmlBug.sPriority.string if xmlBug.sPriority.string else ''
          bug['events'] = []
    
          BACKUP_DIR = 'C:\\Temp\\FBBack'
          BACKUP_DIR += '\\' + xmlBug.sProject.string if xmlBug.sProject.string else ''
    
          if not os.path.exists(BACKUP_DIR):
            os.makedirs(BACKUP_DIR)
    
          BACKUP_DIR += '\\' + str(xmlBug['ixBug'])
    
          if not os.path.exists(BACKUP_DIR):
            os.makedirs(BACKUP_DIR)
    
          for event in xmlBug.events.findAll('event'):
            bugEvent = {}
            bugEvent['ixBugEvent'] = int(event['ixBugEvent'])
            bugEvent['sVerb'] = event.sVerb.string if event.sVerb.string else ''
            bugEvent['dt'] = event.dt.string if event.dt.string else ''
            bugEvent['s'] = event.s.string if event.s.string else ''
            bugEvent['sChanges'] = event.sChanges.string if event.sChanges.string else ''
            bugEvent['evtDescription'] = event.evtDescription.string if event.evtDescription.string else ''
            bugEvent['sPerson'] = event.sPerson.string if event.sPerson.string else ''    
            bugEvent['s'] = event.s.string.encode('ascii', 'ignore').decode('utf-8') if event.s.string else ''
            theAttachments = ''
            for att in event.rgAttachments:
              theAttachments += 'Attachment: ' +att.sFileName.string + '\n'
    
              print('Downloading attachment: ' + att.sFileName.string)
              str1 = att.sURL.string
              str2 = 'ixAttachment='
              loc1 = str1.find(str2) + len(str2)
              loc2 = str1.find('&sFileName')
    
              str3 = ';sFileName='
              loc3 = str1.find(str3) + len(str3)
              loc4 = str1.find('&sTicket')
    
              theURL = S_FOGBUGZ_URL #+ att.sURL.string
              theURL += 'default.asp?'
              theURL += 'ixAttachment=' + str1[loc1:loc2]
              theURL += '&pg=pgDownload&pgType=pgFile&sFilename=' + str1[loc3:loc4]
              theURL += '&token=123456sometoken'
    
              #fix the replace
              newFileName = att.sFileName.string.replace('\\','')
              newFileName = newFileName.replace(':','')
              newFileName = BACKUP_DIR+'\\'+newFileName
              #print(newFileName)
              urllib.request.urlretrieve(theURL, newFileName)
            bugEvent['attachment'] = theAttachments
            bug['events'].append(bugEvent)
    
    
    
    
          f = open('%s\\%s.html' % (BACKUP_DIR,bug['ixBug']),'w')
          f.write(tmpl.render(bug=bug))
          f.close()
          fb.view(ixBug=ixBug)
    
    
    main()
    

    2- fbBackupTemplate.html

    <!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: 90%;
          padding: 5px;
          background: #B8DDFF;
          font: 24px "Trebuchet MS", Arial, Helvetica, sans-serif;
        }
        .bugEvent {
          outline-width: 2px;
          outline-style: solid;
          outline-color: blue;
          width: 90%;
          padding: 5px;
          background: #D2E9FF;
          font: 12px Arial, Helvetica, sans-serif;
        }
    
        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>
            <pre class="s">{{event.attachment|e}}</pre><br>
          </div>
        {% endfor %}
      </div>
    </body>
    </html>
    

    Original URL for the backup script: https://developers.fogbugz.com/?W211

    Get a token for the API call: https://support.fogbugz.com/hc/en-us/articles/360011255754-Generating-a-FogBugz-XML-API-Token