您的位置 首页 > 数码极客

「禅道如何设置bug字段」禅道如何提交bug!

禅道中的bug迁移到jira

笔者最近需要把部分事业部使用的禅道中的bug迁移到公司统一的jira中,经搜索,官方并未提供统一的迁移工具,所以只能自立更生了。

  • 从禅道中导出Bug

  • 禅道本身提供了导出功能,所以我们使用内置的导出将需要迁移的产品/项目的bug进行导出:

为了后续解析方便,我们使用的是xml模板:

  • 在jira中创建项目Test作为目标项目
  • 创建禅道各Bug字段与jira中字段的对应,比如:
status.激活=Open status.已解决=开发完成 status.已关闭=Closed resolution.不予解决=Won't Do resolution.无法重现=Cannot Reproduce resolution.已解决=Done

表中的status对应导出缺陷的status值,resolution对应resolution字段值,priority对应severity值或者priority值

  • 创建用于描述迁移的Zentao class:
public class Zentao { String project; String product; Set<String> components; List<ZentaoEntity> zentaoEntityList; public String getProject() { return project; } public void setProject(String project) { = project; } public String getProduct() { return product; } public void setProduct(String product) { = product; } public Set<String> getComponents() { return components; } public void setComponents(Set<String> components) { = components; } public List<ZentaoEntity> getZentaoEntityList() { return zentaoEntityList; } public void setZentaoEntityList(List<ZentaoEntity> zentaoEntityList) { = zentaoEntityList; } }
  • 创建用于描述每个bug的ZentaoEntity
package zentao; import com.alibaba.; import lombok.ex; import org.a; import org.a; import org.a; import org.a; import org.a; import java.io.File; import java.io.FileOutputStream; import java.u; import java.u; import java.u; import java.u; @Slf4j public class ZentaoEntity { String id; String product; String module; String project; String story; String task; String title; String keywords; String severity; String pri; String type;//bug类型 String os; String browser; String steps; String status; String activatedCount;//没有对应 String confirmd;//是否确认,没有对应 String mailto;//抄送给,没有对应 String openedBy;// String openedDate; String openedBuild; String assignedTo; String assignedDate; String resolvedBy; String resolution; String resolveBuild; String resolveDate; String closedBy; String closedDate; String duplicateBug; String linkBug; String testcase;//用例 String lastEditedBy;//最后修改者,没有对应 String lastEditedDate;//最后修改日期,没有对应 String files;//附件 List<String> attachements = new ArrayList<String>();//下载保存的附件 static final Pattern IMAGE_PATTERN1 = Pa("<img src=\".*?\" alt=\"(.*?)\" />"); static final Pattern IMAGE_PATTERN2 = Pa("<img alt=\"(.*?)\" src=\".*?\" />"); static final Pattern URL_PATTERN = Pa(".*?href='(.*?)'.*?"); public String getId() { return id; } public void setId(String id) { = id; } public String getProduct() { return product; } public void setProduct(String product) { = product; } public String getModule() { return module; } public void setModule(String module) { = module; } public String getProject() { return project; } public void setProject(String project) { = project; } public String getStory() { return story; } public void setStory(String story) { = story; } public String getTask() { return task; } public void setTask(String task) { = task; } public String getTitle() { return title; } public void setTitle(String title) { = title; } public String getKeywords() { return keywords; } public void setKeywords(String keywords) { = keywords; } public String getSeverity() { return severity; } public void setSeverity(String severity) { = severity; } public String getPri() { return pri; } public void setPri(String pri) { = pri; } public String getType() { return type; } public void setType(String type) { = type; } public String getOs() { return os; } public void setOs(String os) { = os; } public String getBrowser() { return browser; } public void setBrowser(String browser) { = browser; } public String getSteps() { return steps; } public void setSteps(String steps) { steps = ("<p>", "").replaceAll("</p>", "") .replaceAll("<div>", "").replaceAll("</div>", "") .replaceAll("<br />", ""); //把steps换位jira格式 Matcher matcher = IMAGE_PATTERN1.matcher(steps); while()){ steps = (0), "!" + ma(1) + "!"); } matcher = IMAGE_PATTERN2.matcher(steps); while()){ steps = (0), "!" + ma(1) + "!"); } = steps; } public String getStatus() { return status; } public void setStatus(String status) { = status; } public String getActivatedCount() { return activatedCount; } public void setActivatedCount(String activatedCount) { = activatedCount; } public String getConfirmd() { return confirmd; } public void setConfirmd(String confirmd) { = confirmd; } public String getMailto() { return mailto; } public void setMailto(String mailto) { = mailto; } public String getOpenedBy() { return openedBy; } public void setOpenedBy(String openedBy) { = openedBy; } public String getOpenedDate() { return openedDate; } public void setOpenedDate(String openedDate) { = openedDate; } public String getOpenedBuild() { return openedBuild; } public void setOpenedBuild(String openedBuild) { = openedBuild; } public String getAssignedTo() { return assignedTo; } public void setAssignedTo(String assignedTo) { = assignedTo; } public String getAssignedDate() { return assignedDate; } public void setAssignedDate(String assignedDate) { = assignedDate; } public String getResolvedBy() { return resolvedBy; } public void setResolvedBy(String resolvedBy) { = resolvedBy; } public String getResolution() { return resolution; } public void setResolution(String resolution) { = resolution; } public String getResolveBuild() { return resolveBuild; } public void setResolveBuild(String resolveBuild) { = resolveBuild; } public String getResolveDate() { return resolveDate; } public void setResolveDate(String resolveDate) { = resolveDate; } public String getClosedBy() { return closedBy; } public void setClosedBy(String closedBy) { = closedBy; } public String getClosedDate() { return closedDate; } public void setClosedDate(String closedDate) { = closedDate; } public String getDuplicateBug() { return duplicateBug; } public void setDuplicateBug(String duplicateBug) { = duplicateBug; } public String getLinkBug() { return linkBug; } public void setLinkBug(String linkBug) { = linkBug; } public String getTestcase() { return testcase; } public void setTestcase(String testcase) { = testcase; } public String getLastEditedBy() { return lastEditedBy; } public void setLastEditedBy(String lastEditedBy) { = lastEditedBy; } public String getLastEditedDate() { return lastEditedDate; } public void setLastEditedDate(String lastEditedDate) { = lastEditedDate; } public String getFiles() { return files; } public void setFiles(String files) { = files; } public String toString(){ return JSON.toJSONString(this); } public List<String> getAttachements() { return attachements; } public void setAttachements(List<String> attachements) { = attachements; } public void attachment(String url, String dir) throws Exception{ log.info("下载{}", url); File file = new File(dir + File.separator + url.substring("/") + 1)); ().mkdirs(); .add()); if(!()){ (); } else{ return; } CloseableHttpClient client = H(); HttpGet httpGet = new HttpGet(url); IOU(httpGet).getEntity().getContent(), new FileOutputStream(file)); client.close(); } public void attachments(String url, String dir) throws Exception{ Matcher matcher= URL_PATTERN.matcher(url); while()){ attachment(1), dir); } } }

这里有几个点需要注意:

1、用于描述缺陷步骤的steps,禅道会以<img>标签作为内容,而在jira中,是以!***.png!的格式放进去,所以我们在设置steps的时候,使用正则表达式将禅道格式替换为Jira格式。另外在实际迁移过程中发现,禅道中有时候存在<img alt=... src... />或者<img src=... alt=... />两种格式,所以需要对两种情况都进行替换。同时,对步骤中的无用标签<p>、<div>、<br />等进行了替换;

2、禅道bug的附件放在<files>标签中,我们在解析的时候,可以通过该标签获取并下载附件,然后上传到jira中,从而解决了附件的迁移。

  • 禅道bug的解析

​ 这里没有特殊的地方,主要是使用dom4j进行xml文件的解析:

package zentao; ​ import com.alibaba.; import lombok.ex; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; ​ import java.io.FileInputStream; import java.util.*; ​ @Slf4j public class Parser { public Zentao doParse(String filePath) throws Exception{ Zentao zentao = new Zentao(); SAXReader reader = new SAXReader(); Document document = reader.read(new FileInputStream(filePath)); Element root = document.getRootElement(); Iterator iterator = root.element("rows").elements("row").iterator(); List<ZentaoEntity> list = new ArrayList<ZentaoEntity>(); zen(list); HashSet<String> products = new HashSet<String>(); HashSet<String> projects = new HashSet<String>(); Set<String> modules = new HashSet<String>(); zen(modules); while()){ Element element = (Element) i(); ZentaoEntity entity = new ZentaoEntity(); li(entity); en("id")); en("product")); ("product")); ​ en("module")); modules.add()); ​ en("project")); ()); ​ en("story")); ​ en("task")); ​ en("title")); ​ en("keywords")); ​ en("severity")); ​ en("pri")); ​ en("type")); ​ en("os")); ​ en("browser")); ​ en("steps")); ​ en("status")); ​ en("activatedCount")); ​ en("confirmed")); ​ en("mailto")); ​ en("openedBy")); ​ en("openedDate")); ​ en("openedBuild")); ​ en("assignedTo")); ​ en("assignedDate")); ​ en("resolvedBy")); ​ en("resolution")); ​ en("resolvedBuild")); ​ en("resolvedDate")); ​ en("closedBy")); ​ en("closedDate")); ​ en("duplicateBug")); ​ en("linkBug")); ​ en("case")); ​ en("lastEditedBy")); ​ en("lastEditedDate")); ​ en("files")); en("files"), "attachments"); ​ log.info()); } String p = ""; i() == 1){ log.info("使用project"); p = ().next(); } else i() == 1){ log.info("使用products"); p = ().next(); } else{ throw new Exception("无法确定用谁, products=" + JSON.toJSONString(projects) + ", projects=" + JSON.toJSONString(projects)); } log.info(">>> 需要创建的项目为:{}", p); zen(p); zen(p); return zentao; } } ​
  • Jira的导入
  • 在导入jira的时候,主要遵循两个原则,能用api的直接使用api;不能使用api的,直接操作数据库。核心代码如下:
package zentao; ​ import com.my; import lombok.ex; import net.rcarz.jiraclient.*; import org.a; import java.io.File; import java.io.FileReader; import java.; import java.; import java.; import java.; import java.u; import java.u; import java.u; import java.u; ​ @Slf4j public class JiraLoader { private JiraClient jiraClient; private PropertiesConfiguration propertiesConfiguration; private Connection connection; private Map<String, Integer> priorityMap = new HashMap<String, Integer>(); private Map<String, Integer> resolutionMap = new HashMap<String, Integer>(); private Map<String, Integer> statusMap = new HashMap<String, Integer>(); private Map<String, String> componenetMap = new HashMap<String, String>(); ​ public JiraLoader(String url, String user, String pass){ BasicCredentials credentials = new BasicCredentials(user, pass); jiraClient = new JiraClient(url, credentials); try{ propertiesConfiguration = new PropertiesConfiguration(); String path = getClass().getClassLoader().getResource("zen;).getFile(); (new FileReader(new File(path))); ​ connection = DriverManager.getConnection("jdbc:mysql://192.168.1.1/jira", "root", "test"); ​ String sql = "SELECT pname, ID FROM priority"; ResultSet res = connec().executeQuery(sql); while()){ ("pname"), res.getInt("ID")); } ​ sql = "SELECT pname, ID from resolution"; res = connec().executeQuery(sql); while()){ re("pname"), res.getInt("ID")); } ​ sql = "SELECT pname, ID from issuestatus"; res = connec().executeQuery(sql); while()){ ("pname"), res.getInt("ID")); } }catch(Exception e){ log.error(), e); } } ​ private void createComponents(Set<String> components, String projectKey) throws Exception{ List<Component> componentList = jiraClient.getProject(projectKey).getComponents(); for(String component : components){ boolean exists = false; for(Component c : componentList){ i().equalsIgnoreCase(component)){ log.info("Component:{}已经存在", component); exists = true; break; } } ​ if(!exists) { jiraClient.createComponent(projectKey) .name(component) .execute(); log.info("添加component成功:{}", component); } } ​ //获取最终的component 列表 componentList = jiraClient.getProject(projectKey).getComponents(); for(Component c : componentList){ com(), c.getId()); } } ​ private void createIssue(List<ZentaoEntity> list, String projectKey) throws Exception{ for(ZentaoEntity entity : list) { Issue issue = jiraClient.createIssue(projectKey, "Bug") .field, en()) .field, en()) .execute(); for(String attachment : en()) { i(new File(attachment)); } log.info("创建issue:{}", i()); ​ fix(issue, entity); } } ​ public void load(Zentao zentao, String projectKey) throws Exception{ createComponen(), projectKey); createIssue(), projectKey); } ​ public void fix(Issue issue, ZentaoEntity entity) throws Exception{ String id = i(); String sql; PreparedStatement ps; ​ int priority = ("priority." + en())); ​ ​ if(!S())){ sql= "UPDATE jiraissue SET PRIORITY=?,RESOLUTION=?,CREATED=?,UPDATED=?,RESOLUTIONDATE=?,ISSUEStatus=?,REPORTER=?,ASSIGNEE=? WHERE id=?"; ps = connec(sql); (1, priority); ​ int resolution = re("resolution." + en())); int status = ("status." + en())); (2, resolution); (3, en()); (4, en()); (5, en()); (6, status); (7, en()); (8, en()); (9, id); } else{ sql= "UPDATE jiraissue SET PRIORITY=?,CREATED=?,ISSUEStatus=?,REPORTER=?,ASSIGNEE=? WHERE id=?"; ps = connec(sql); (1, priority); (2, en()); int status = ("status." + en())); (3, status); (4, en()); (5, en()); (6, id); } ​ (); ​ //更改component if(!S())){ sql = "INSERT INTO nodeassociation(`SOURCE_NODE_ID`,`SOURCE_NODE_ENTITY`,`SINK_NODE_ID`,`SINK_NODE_ENTITY`,`ASSOCIATION_TYPE`) "; sql += " VALUES(?,'Issue',?,'Component', 'IssueComponent')"; log.info("更新所属模块:{}", en()); ps = connec(sql); (1, i()); (2, com())); (); } } } ​

代码都比较简单,我就不逐行解释了,用一个流程图来描述下整个迁移过程:

责任编辑: 鲁达

1.内容基于多重复合算法人工智能语言模型创作,旨在以深度学习研究为目的传播信息知识,内容观点与本网站无关,反馈举报请
2.仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证;
3.本站属于非营利性站点无毒无广告,请读者放心使用!

“禅道如何设置bug字段,禅道如何提交bug,禅道如何关闭bug,禅道如何导出bug”边界阅读