Yodiz is an integrated Scrum and bug manager that I have used for a while. For a variety of reasons, largely irrelevant to this post, I decided to move to Pivotal Tracker, instead. Hence I was faced with the necessity to migrate outstanding issues from Yodiz to Pivotal Tracker.

Now, both Yodiz and Pivotal allow import/export of issues via CSV. However the two CSV formats and metadata required are significantly different. I needed to create a migration/conversion script to make things work. After some messing with both formats the migration script was ready. I am publishing it here in case it may save somebody some time.

Disclaimer: this is a [very] quick and [very] dirty work, in no way generic or complete. It may, however, save you several hours if you need to do something similar. Feel free to modify and use in any way. Distributed under MIT license.

How to use:

  1. Copy/paste package.json and csv.js into some folder
  2. Run npm install
  3. Edit the head part of csv.js to modify locations of yodiz and pivotal CSV files. Take a good look at what the script does to make sure it makes sense for you
  4. Run: ./csv.js from bash prompt

package.json:

{
  "name": "yodiz_csv_import_for_pivotal",
  "version": "1.0.0",
  "description": "",
  "main": "csv.js",
  "dependencies": {
    "csv-parse": "^0.0.6",
    "fs": "^0.0.2",
    "stream-transform": "^0.0.6",
    "underscore": "^1.7.0",
    "underscore.string": "^3.0.0"
  },
  "devDependencies": {},
  "author": "Irakli Nadareishvili",
  "license": "MIT"
}

csv.js

#!/usr/bin/env node

var basePath = __dirname,
    yodizFilename = '/bugs-yodiz.csv',
    pivotalFilename = '/bugs-pivotal.csv';

/*****  End of configuration ****/

var fs        = require('fs');
var parse     = require('csv-parse');
var transform = require('stream-transform');
var _         = require('underscore');
var _s        = require("underscore.string");

var input  = fs.createReadStream(basePath + yodizFilename);
var myFile = fs.createWriteStream(basePath + pivotalFilename);

console.log("Starting Migration from: " + basePath + yodizFilename); 

var output = [];
var parser = parse({delimiter: ','})

var fakeBugId = 0;
var migration_date = "Jan 17, 2015"; // Yodiz doesn't export dates. Faking.

var pivotal = "";
var old_bug_no, title, description, status, created, responsible, description_url;

var all_yodiz_statuses = ['New', 'In progress', 'Resolved', 'In Verification', 
                          'Closed', 'Rejected', 'Reopened'];
var pivotal_statuses = ['unstarted', 'started', 'finished', 'delivered', 
                          'accepted', 'rejected', 'started'];


function parse_status(yodiz_status) {
  yodiz_status = _s.trim(yodiz_status);
  var position = _.indexOf(all_yodiz_statuses, yodiz_status);
  var pivotal_status = pivotal_statuses[position];

  return pivotal_status; 
}

var transformer = transform(function(record, callback) {    

    old_bug_no  = record[0];    
    title       = record[1].replace(/"/g, '""');
    description = record[2].replace(/"/g, '""');
    status      = parse_status(record[6]);

    created     = record[9];
    responsible = record[10];

    description_url = '';

    pivotal = fakeBugId + ',"[' + old_bug_no + '] ' + title + '",' + // 2
              '"",1,"' + migration_date + '","' + migration_date + // 5
              '","bug","",' + status + ',' + // 5
              '"' + migration_date + '","","","' + created + '",' + // 4
              '"' + description + '","' + description_url +  // 2
              '","' + responsible + '"' + "\n"; // 1  

    if (old_bug_no == 'ID')  { // Title line
      pivotal = 'Id,Title,Labels,Iteration,Iteration Start,Iteration End,Type,Estimate,Current State,Created at,Accepted at,Deadline,Requested By,Description,URL,Owned By' + "\n";
    }

    if (status === 'accepted') { // I had no need to import closed issues
      pivotal = '';
    }

    callback(null, pivotal);  

}, {parallel: 10});

input.pipe(parser).pipe(transformer).pipe(myFile);

input.on("error", function(err, callback) {
  console.dir(err);
});

myFile.on("finish", function(err) {
  console.log("Finished, Pivotal CSV for bugs is in: " + basePath + yodizFilename);
});