- Summary
- Status
- Description
- Milestone (as a custom JIRA field)
- Assignee
- Reporter
- Resolution (if resolved)
- Resolution description (as a comment)
- Creation time
- Resolved time (if resolved)
- existingprojectkey
- user.email.suffix
- Time of day for creation/resolved
- Comments
Java class UnfuddleToJira.java:
// 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();
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));
}
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();
}
}
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;
@Override
public int compareTo(Ticket 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();
}
}
Configuration file:
# written by PropertiesConfiguration # Wed May 05 07:12:57 UTC 2010 existingprojectkey = WEB importsingleproject = false importexistingproject = true mapfromcsv = false field.Resolution = resolution field.Milestone = customfield_Milestone:select field.Assignee = assignee field.Summary = summary field.Status = status field.Description = description field.Reporter = reporter field.CreateTime = created value.Status.closed = 6 value.Resolution.works_for_me = 5 value.Resolution.will_not_fix = 2 value.Status.new = 1 value.Status.reassigned = 1 value.Resolution.invalid = 4 value.Resolution.postponed = 2 value.Status.accepted = 3 value.Resolution.fixed = 1 value.Resolution.duplicate = 3 user.email.suffix = @kikini.com date.import.format = yyyyMMdd field.ResolveTime = resolutiondate date.fields = CreateTime date.fields = ResolveTime