Skip to content

Instantly share code, notes, and snippets.

@obra
Forked from jmreidy/env.sample.js
Created December 30, 2011 19:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save obra/1541205 to your computer and use it in GitHub Desktop.
Save obra/1541205 to your computer and use it in GitHub Desktop.
Pivotal to Sprintly importer
module.exports = {
pivotal: {
TOKEN: 'TOKEN'
PID: 'PID',
},
sprintly: {
USER: "USER_EMAIL",
ID: 'PRODUCT_ID',
KEY: 'API_KEY'
},
}
var http = require("http"),
https = require("https"),
parser = require("xml2json"),
fs = require("fs"),
qs = require("querystring"),
env = require(__dirname+"/env");
var pivotalOptions = {
host: 'www.pivotaltracker.com',
port: 443,
path: "/services/v3/projects/"+env.pivotal.PID+"/stories",
headers: { "X-TrackerToken": env.pivotal.TOKEN}
};
var sprintlyOptions = {
hostname: 'sprint.ly',
path: '/api/products/'+env.sprintly.ID+'/items.json',
auth: env.sprintly.USER+':'+env.sprintly.KEY,
method: 'POST'
}
var getPivotalStories = function(options) {
if (options.local) {
fs.readFile("pivotalData.xml", "utf-8",function(err,response) {
handleStories(parser.toJson(response, {object:true}));
});
}
else {
var response = '';
https.get(pivotalOptions, function(res) {
res.on('data', function (chunk) {
response += chunk;
});
res.on('end', function() {
fs.writeFileSync("pivotalData.xml", response);
handleStories(parser.toJson(response, {object:true}));
});
}).on('error', function(e) {
console.log("Got error: " + e.message);
});
}
};
var addToSprintly = function(story) {
var options = sprintlyOptions;
options['headers'] = {
"Content-Type": 'application/x-www-form-urlencoded',
"Content-Length": story.length
};
request = https.request(options, function(res) {
var response = '';
res.on('data', function(chunk) {
response += chunk;
});
res.on('end', function() {
console.log("Story Added!", response);
});
});
request.write(story);
request.end();
}
var handleStories = function(data) {
var stories = data.stories.story;
stories = parseStories(stories);
var j = 0;
var poll = setInterval(function() {
if ( j < stories.length) {
addToSprintly(qs.stringify(stories[j]));
j++;
}
else {
clearInterval(poll);
}
},250);
}
var parseStories = function(stories) {
var whatSearch = new RegExp(/I want (to [\w\s\.\'\"]+)/i);
var whySearch = new RegExp(/In order to ([\w\s\.\'\"]+)/i);
var whoSearch = new RegExp(/As an? (\w+)/i);
var addStory = function(feature) {
description = feature.description;
if(typeof description.replace === 'undefined') {
description = feature.name;
}
description = description.replace(/(As|I)/g, function(str) {
return ","+str;
});
description = description.replace("-","");
return {
type: "story",
who: translate(description, whoSearch),
what: whatTranslate(description, whatSearch,feature.name),
why: "I can "+translate(description, whySearch),
score: parseScore(feature.estimate),
tags: feature.labels,
status: mapStatus(feature.current_state)
};
}
var addDefect = function(bug) {
return {
type: "defect",
title: bug.name,
tags: bug.labels,
status: mapStatus(bug.current_state)
}
}
var addTask = function(pivotalTask) {
return {
type: "task" ,
title: pivotalTask.name,
tags: pivotalTask.labels,
status: mapStatus(pivotalTask.current_state)
};
}
var translate = function(description, re) {
var result = re.exec(description);
if (result && result.length > 0) {
return result[1]
}
else {
return "unknown";
}
};
var whatTranslate = function(description, re,title) {
var result = re.exec(description);
if (result && result.length > 0) {
return result[1]
}
else {
return title;
}
};
var mapStatus = function(pivotal_state) {
var result = 'backlog';
if(pivotal_state === 'unstarted') { result = 'backlog'}
else if(pivotal_state === 'unscheduled') { result = 'backlog'}
else if(pivotal_state === 'started') { result = 'in-progress'}
else if(pivotal_state === 'delivered') { result = 'completed'}
else if(pivotal_state === 'accepted') { result = 'accepted'}
else {
console.log("I have no idea what to do with a pivotal state of "+pivotal_state);
}
return result;
}
var parseScore = function(score) {
var result = +score['$t']
if (!result || result == -1) result = "~";
else if (result==1) result = "S"
else if (result==2) result = "M"
else if (result==4) result = "L"
else if (result==8) result = "XL"
return result;
}
return stories.map (function(story) {
switch(story.story_type) {
case 'feature':
return addStory(story);
case 'bug':
return addDefect(story);
case 'chore':
return addTask(story);
}
});
}
getPivotalStories({local:true});
{
"author": "Justin Reidy <jmreidy@rzrsharp.net> (http://rzrsharp.net)",
"name": "pivotal-to-sprintly",
"description": "Convert Pivotal Tracker stories to Sprint.ly stories",
"version": "0.0.1",
"repository": {
"url": ""
},
"engines": {
"node": "~0.6.0"
},
"dependencies": {
"xml2json" : "0.2.x",
"querystring": "0.1.x"
},
"devDependencies": {}
}
@jwaldrip
Copy link

I am new to node any idea what is going on here?


Jasons-MacBook-Pro:pivitol_importer jwaldrip$ node importer.js 

/Users/jwaldrip/dev/pivitol_importer/node_modules/xml2json/node_modules/node-expat/lib/node-expat.js:24
    return this.parser.parse(buf, isFinal);
                       ^
TypeError: Parse buffer must be String or Buffer
    at [object Object].parse (/Users/jwaldrip/dev/pivitol_importer/node_modules/xml2json/node_modules/node-expat/lib/node-expat.js:24:24)
    at Object.toJson (/Users/jwaldrip/dev/pivitol_importer/node_modules/xml2json/lib/xml2json.js:81:17)
    at /Users/jwaldrip/dev/pivitol_importer/importer.js:26:34
    at [object Object].<anonymous> (fs.js:98:14)
    at [object Object].emit (events.js:64:17)
    at [object Object].<anonymous> (fs.js:92:18)
    at [object Object].emit (events.js:67:17)
    at Object.oncomplete (fs.js:1077:12)
Jasons-MacBook-Pro:pivitol_importer jwaldrip$ 

@obra
Copy link
Author

obra commented May 31, 2012 via email

@flynfish
Copy link

getting same error. @jwaldrip did you figure it out?

@apangeajwrubel
Copy link

Figured this out. Change L148 to:

getPivotalStories({local:false});

I think you can export the stories as a local file and store them in the same directory - passing local:false tells the script to hit Pivotal's API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment