Created
April 24, 2014 22:10
-
-
Save acrollet/11271227 to your computer and use it in GitHub Desktop.
Unfuddle to Jira, with comments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Original author Gabe Nell. Released under the Apache 2.0 License | |
// http://www.apache.org/licenses/LICENSE-2.0.html | |
import java.io.FileOutputStream; | |
import java.io.PrintStream; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import org.joda.time.DateTime; | |
import org.joda.time.format.DateTimeFormat; | |
import org.joda.time.format.DateTimeFormatter; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
public class UnfuddleToJira { | |
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("yyyyMMdd"); | |
private final Document doc; | |
private final PrintStream output; | |
private final Map<String, String> milestones; | |
private final Map<String, String> people; | |
public UnfuddleToJira(Document doc, PrintStream output) { | |
this.doc = doc; | |
this.output = output; | |
this.milestones = parseMilestones(doc); | |
this.people = parsePeople(doc); | |
} | |
private static Map<String, String> parseMilestones(Document doc) { | |
Map<String, String> milestones = new HashMap<String, String>(); | |
NodeList milestoneNodes = doc.getElementsByTagName("milestone"); | |
for (int i = 0; i < milestoneNodes.getLength(); i++) { | |
Element elem = (Element)milestoneNodes.item(i); | |
String title = elem.getElementsByTagName("title").item(0).getTextContent().replace(" ", ""); | |
String id = elem.getElementsByTagName("id").item(0).getTextContent(); | |
milestones.put(id, title); | |
} | |
System.out.println("Found " + milestones.size() + " milestones: " + milestones); | |
return milestones; | |
} | |
private static Map<String, String> parsePeople(Document doc) { | |
Map<String, String> people = new HashMap<String, String>(); | |
NodeList peopleNodes = doc.getElementsByTagName("person"); | |
for (int i = 0; i < peopleNodes.getLength(); i++) { | |
Element elem = (Element)peopleNodes.item(i); | |
String name = elem.getElementsByTagName("username").item(0).getTextContent(); | |
String id = elem.getElementsByTagName("id").item(0).getTextContent(); | |
people.put(id, name); | |
} | |
System.out.println("Found " + people.size() + " people: " + people); | |
return people; | |
} | |
private static String prepareForCsv(String input) { | |
if (input == null) return ""; | |
return "\"" + input.replace("\"", "\"\"") + "\""; | |
} | |
private static String convertDate(String input) { | |
return DATE_FORMATTER.print(new DateTime(input)); | |
} | |
private String lookupUser(String id) { | |
String person = people.get(id); | |
/** | |
* Here you can transform a person's username if it changed between | |
* Unfuddle and JIRA. Eg: <tt> | |
* if ("gabe".equals(person)) { | |
* person = "gabenell"; | |
* } | |
* </tt> | |
*/ | |
return person; | |
} | |
private String lookupMilestone(String id) { | |
return milestones.get(id); | |
} | |
private void writeCsvHeader() { | |
StringBuilder builder = new StringBuilder(256); | |
builder.append("Summary, "); | |
builder.append("Status, "); | |
builder.append("Assignee, "); | |
builder.append("Reporter,"); | |
builder.append("Resolution,"); | |
builder.append("CreateTime,"); | |
builder.append("ResolveTime,"); | |
builder.append("Milestone,"); | |
builder.append("Description"); | |
output.println(builder.toString()); | |
} | |
private void writeCsvRow(Ticket ticket) { | |
StringBuilder builder = new StringBuilder(256); | |
builder.append(prepareForCsv(ticket.summary)).append(", "); | |
builder.append(prepareForCsv(ticket.status)).append(", "); | |
builder.append(prepareForCsv(lookupUser(ticket.assigneeId))).append(", "); | |
builder.append(prepareForCsv(lookupUser(ticket.reporterId))).append(", "); | |
builder.append(prepareForCsv(ticket.resolution)).append(", "); | |
builder.append(prepareForCsv(convertDate(ticket.createdTime))).append(", "); | |
String resolveTime = ticket.resolution != null ? convertDate(ticket.lastUpdateTime) : null; | |
builder.append(prepareForCsv(resolveTime)).append(", "); | |
builder.append(prepareForCsv(lookupMilestone(ticket.milestoneId))).append(", "); | |
builder.append(prepareForCsv(ticket.description)); | |
// JIRA doesn't have the notion of a resolution description, add it as a | |
// comment | |
if (ticket.resolutionDescription != null) { | |
builder.append(",").append(prepareForCsv(ticket.resolutionDescription)); | |
} | |
builder.append(",").append(ticket.comments); | |
output.println(builder.toString()); | |
} | |
public void writeCsv() throws Exception { | |
NodeList ticketNodes = doc.getElementsByTagName("ticket"); | |
List<Ticket> tickets = new ArrayList<Ticket>(); | |
for (int i = 0; i < ticketNodes.getLength(); i++) { | |
Node node = ticketNodes.item(i); | |
Element nodeElem = (Element)node; | |
Ticket ticket = new Ticket(); | |
NodeList ticketElements = nodeElem.getChildNodes(); | |
for (int j = 0; j < ticketElements.getLength(); j++) { | |
Node ticketSubNode = ticketElements.item(j); | |
String nodeName = ticketSubNode.getNodeName(); | |
if ("id".equals(nodeName)) { | |
ticket.id = ticketSubNode.getTextContent(); | |
} else if ("status".equals(nodeName)) { | |
ticket.status = ticketSubNode.getTextContent(); | |
} else if ("summary".equals(nodeName)) { | |
ticket.summary = ticketSubNode.getTextContent(); | |
} else if ("description".equals(nodeName)) { | |
ticket.description = ticketSubNode.getTextContent(); | |
} else if ("milestone-id".equals(nodeName)) { | |
ticket.milestoneId = ticketSubNode.getTextContent(); | |
} else if ("assignee-id".equals(nodeName)) { | |
ticket.assigneeId = ticketSubNode.getTextContent(); | |
} else if ("reporter-id".equals(nodeName)) { | |
ticket.reporterId = ticketSubNode.getTextContent(); | |
} else if ("resolution".equals(nodeName)) { | |
ticket.resolution = ticketSubNode.getTextContent(); | |
} else if ("resolution-description".equals(nodeName)) { | |
ticket.resolutionDescription = ticketSubNode.getTextContent(); | |
} else if ("created-at".equals(nodeName)) { | |
ticket.createdTime = ticketSubNode.getTextContent(); | |
} else if ("updated-at".equals(nodeName)) { | |
ticket.lastUpdateTime = ticketSubNode.getTextContent(); | |
} else if ("comments".equals(nodeName)) { | |
if (ticketSubNode instanceof Element) { | |
String commentText=""; | |
Element commentsElement = (Element)ticketSubNode; | |
NodeList commentElements = commentsElement.getElementsByTagName("comment"); | |
for (int k = 0; k < commentElements.getLength(); k++) { | |
Node commentSubNode = commentElements.item(k); | |
String commentNodeName = commentSubNode.getNodeName(); | |
NodeList commentChildElements = commentSubNode.getChildNodes(); | |
Comment comment = new Comment(); | |
for (int l = 0; l < commentChildElements.getLength(); l++) { | |
Node commentChildSubNode = commentChildElements.item(l); | |
String commentChildNodeName = commentChildSubNode.getNodeName(); | |
if ("id".equals(commentChildNodeName)) { | |
comment.id = commentChildSubNode.getTextContent(); | |
} | |
else if ("author-id".equals(commentChildNodeName)) { | |
comment.authorId = commentChildSubNode.getTextContent(); | |
} | |
else if ("created-at".equals(commentChildNodeName)) { | |
comment.createdTime = convertDate(commentChildSubNode.getTextContent()); | |
} | |
else if ("body".equals(commentChildNodeName)) { | |
comment.description = commentChildSubNode.getTextContent(); | |
} | |
} | |
if (comment.description.length() > 0) { | |
commentText += prepareForCsv(comment.createdTime + ";" + lookupUser(comment.authorId) + ";" + comment.description); | |
commentText += ","; | |
} | |
} | |
ticket.comments = commentText; | |
} | |
} | |
} | |
tickets.add(ticket); | |
} | |
System.out.println("Writing " + tickets.size() + " tickets..."); | |
// Output to CSV in order of ticket number | |
writeCsvHeader(); | |
Collections.sort(tickets); | |
for (Ticket ticket : tickets) { | |
writeCsvRow(ticket); | |
} | |
} | |
public static class Ticket implements Comparable<Ticket> { | |
public String id; | |
public String summary; | |
public String status; | |
public String description; | |
public String milestoneId; | |
public String assigneeId; | |
public String reporterId; | |
public String resolution; | |
public String resolutionDescription; | |
public String createdTime; | |
public String lastUpdateTime; | |
public String comments; | |
@Override | |
public int compareTo(Ticket other) { | |
return Integer.parseInt(id) - Integer.parseInt(other.id); | |
} | |
} | |
public static class Comment implements Comparable<Comment> { | |
public String id; | |
public String description; | |
public String authorId; | |
public String createdTime; | |
@Override | |
public int compareTo(Comment other) { | |
return Integer.parseInt(id) - Integer.parseInt(other.id); | |
} | |
} | |
public static void main(String[] args) throws Exception { | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setNamespaceAware(true); | |
if (args.length != 2) { | |
System.err.println("Usage: UnfuddleToJira /path/to/unfuddle/backup.xml /path/to/jira/output.csv"); | |
return; | |
} | |
String inputFilename = args[0]; | |
String outputFilename = args[1]; | |
PrintStream output = new PrintStream(new FileOutputStream(outputFilename), true, "UTF-8"); | |
UnfuddleToJira converter = new UnfuddleToJira(factory.newDocumentBuilder().parse(inputFilename), output); | |
converter.writeCsv(); | |
output.close(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment