- 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