Exporting case details from FogBugz

1.3k views Asked by At

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.

1

There are 1 answers

2
Paul On BEST ANSWER

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