FlexDeploy has out of box integration with Jira, but you can easily integrate with other issue tracking systems. Such third party issue tracking system integration can be enabled using Java or Groovy implementation.
Go to Issue Tracking Systems page using Administration - Integrations - Issue Tracking Systems menu.
Let's look at more details on how to create a custom issue tracking system integration with Flex Deploy. Click Create to implement integration with custom issue tracking system.
Let's define an example issue tracking system in FlexDeploy. You can provide implementation as Java class or just Groovy script. Groovy script would allow for dynamic update whereas use of Java code will require restart of server.
Here we are creating custom issue tracking system with the properties, you can add more as necessary.
Implementation must implement the following API operations to integrate with FlexDeploy.
Method Name | Parameter(s) | Return Type | Description |
---|---|---|---|
populateTicket | Ticket pTicket | void | This method should call the issue Tracking System using the ticket number (pTicket.getNumber()) and set the Description and Type |
getTicketURL | Ticket pTicket | String | This method should construct the URL to access the ticket. (Ex http://<hostname>:<port>/redmine/issues/<issuenumber>.json) |
addCommentToTicket | Ticket pTicket, String pComment | void | Adding pComment to the ticket. The ticket number (pTicket.getNumber()) from Ticket object should be used to get the Ticket and add the comment. |
changeTicketStatusTo | Ticket pTicket, String pStatus | void | Change the status of the ticket to pStatus. The ticket number (pTicket.getNumber()) from Ticket object should be used to get the Ticket and change the status. |
getTicketStatus | Ticket pTicket | String | Get the current status of the ticket. The ticket number (pTicket.getNumber()) from Ticket object should be used to get the Ticket status. |
checkConnection | void | This should invoke any status or heath check URL of the issue tracking system to ensure the system is up and running | |
parseTicketNumberFromChangeLogs | String pProjectName, List<String> pMessagesToParse, List<String> pTicketPatternList | Collection<String> | pProjectName - name of the project for which the Issue tracking system is configured pMessageToParse - list of string from the SCM commit logs pTicketPatternList - pattern configured in the project or for the issues tracking system. Add a custom rule by project to parse the message log string using single or multiple patterns. You can also use a different pattern list. Should return a unique list of ticket numbers |
Here are high level steps for Java implementation. You can use any IDE to prepare this implementation.
package mycompany.extension.redmine.impl; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.ws.rs.core.Response; import flexagon.fd.model.integration.its.api.IssueTrackingSystem; import flexagon.fd.model.integration.its.api.Ticket; import flexagon.fd.model.integration.util.ApiException; public class RedmineServiceIntegration extends IssueTrackingSystem { public RedmineServiceIntegration() { super(); } @Override public void checkConnection() throws ApiException { String methodName = "checkConnection"; System.out.println(methodName + " getting version for project to check connection"); ClientResponse clientResponse = getWebResource("/projects/1/versions.json").get(ClientResponse.class); System.out.println(methodName + String.format(" Successfully invoked test connection URL %s", clientResponse)); int statusCode = clientResponse.getStatusInfo().getStatusCode(); if (statusCode == 401) { throw new ApiException("Invalid credentials.", ""); } if (!(clientResponse.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL)) { throw new ApiException(clientResponse.getEntity(String.class), clientResponse.getStatusInfo().getReasonPhrase()); } System.out.println(methodName + " Test connection response code looks valid, check content of response"); System.out.println(methodName + " Validated that JSON data was received from test connction URL invocation."); } private StringBuilder getRedmineURLBuilder() { StringBuilder urlBuilder = new StringBuilder((String) getProperties().get("REDMINE_URL")); return urlBuilder; } private String getRedmineURL() { return getRedmineURLBuilder().toString(); } private String getUserName() { return (String) getProperties().get("REDMINE_USER_NMAE"); } private String getPassword() { return (String) getProperties().get("REDMINE_PASSWORD"); } @Override public void populateTicket(Ticket pTicket) throws ApiException { // TODO Implement this method } @Override public String getTicketURL(Ticket pTicket) throws ApiException { // TODO Implement this method return null; } @Override public void addCommentToTicket(Ticket pTicket, String pString) throws ApiException { // TODO Implement this method } @Override public void changeTicketStatusTo(Ticket pTicket, String pString) throws ApiException { // TODO Implement this method } @Override public String getTicketStatus(Ticket pTicket) throws ApiException { // TODO Implement this method return null; } private Client getRestClient() { Client restClient = Client.create(); restClient.addFilter(new HTTPBasicAuthFilter(getUserName(), getPassword())); restClient.setReadTimeout(20000); restClient.setConnectTimeout(10000); return restClient; } public WebResource getWebResource(String resource) throws ApiException { String methodName = "getWebResource"; if (resource != null && !resource.isEmpty() && !resource.startsWith("/")) { resource += "/" + resource; } WebResource webResource = null; try { webResource = getRestClient().resource(getRedmineURL()).path(resource); } catch (Exception e) { throw new ApiException(e.getMessage(), e.getMessage()); } return webResource; } } |
Here are high level steps for Groovy implementation. You can use any IDE to prepare this implementation.
As groovy is able to access FlexDeploy variables and Java classes, you can take advantage of Java libraries from Groovy script. For example, if there is Java library used to access the issue tracking system, you can places those in lib folder and use those classes from Groovy script. This allows you to keep dynamic part of implementation in Groovy and use Java library.
import flexagon.fd.model.integration.its.api.Ticket; import flexagon.fd.model.integration.util.ApiException; class RedmineIssueTrackingSystem { def checkConnection() { String methodName = "checkConnection()"; try { fdrestutils.testConnection(REDMINE_URL, "/projects/1/versions.json", REDMINE_USER_NMAE, REDMINE_PASSWORD) } catch (Exception e) { log.logInfo(methodName, " Failed in Test connection" + e.getMessage() + " " + e) throw new ApiException("Connection failed. " + e.getMessage()); } } String getTicketStatus(Ticket ticket) { String methodName = "getTicketStatus()"; String message = " ticket# ${ticket.toString()}" log.logInfo(methodName, message); String resourcePath = REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) javax.json.JsonObject jsonObj = fdrestutils.getRequest(REDMINE_URL, resourcePath, REDMINE_USER_NMAE, REDMINE_PASSWORD); if(jsonObj != null) { javax.json.JsonObject issue = jsonObj.getJsonObject("issue") if(issue!=null) { javax.json.JsonObject status = issue.getJsonObject("status") if(status !=null) { return status.getString("name") } } } return null; } String getTicketURL(Ticket ticket) { String methodName = "getTicketURL()"; String message = "ticket# ${ticket.toString()}" log.logInfo(methodName, message); String resourcePath = REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) message = "Redmine ticket URL is # ${REDMINE_URL}, resourcePath=${resourcePath}" log.logInfo(methodName, message) return REDMINE_URL + resourcePath; } def populateTicket(Ticket ticket) { String methodName = "populateTicket()"; String message = "Populate ticket# ${ticket.toString()}" log.logInfo(methodName, message) String resourcePath = REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) javax.json.JsonObject jsonObj = fdrestutils.getRequest(REDMINE_URL, resourcePath, REDMINE_USER_NMAE, REDMINE_PASSWORD); if(jsonObj != null) { javax.json.JsonObject issue = jsonObj.getJsonObject("issue") if(issue!=null) { ticket.setDescription(issue.getString("description")) javax.json.JsonObject tracker = issue.getJsonObject("tracker") if(tracker !=null) { ticket.setType(tracker.getString("name")) } } } } def addCommentToTicket(Ticket ticket, String pComment) { String methodName = "addCommentToTicket()"; try { String message = " Adding comment to ${ticket.toString()} , comment=${pComment}" log.logInfo(methodName, message) def builder = new groovy.json.JsonBuilder() def root = builder.issue { notes "${pComment}" } String payload = builder.toString(); String resourcePath = REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload); } catch (Exception e) { log.logInfo( methodName, " Failed while adding comment to the issue - " + e.getMessage() + " " + e) throw new ApiException("Connection failed. " + e.getMessage()); } } def changeTicketStatusTo(Ticket ticket, String pStatus) { String methodName = "changeTicketStatusTo()"; String message = " Adding comment to ${ticket.toString()} , status=${pStatus}" log.logInfo(methodName, message) def builder = new groovy.json.JsonBuilder() def root = builder.issue { notes "Status updated to ${pStatus}" status_id "${pStatus}" } String payload = builder.toString(); String resourcePath = REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload); } def parseTicketNumberFromChangeLogs(String pProjectName, List<String> pChangeLogMessages, List<String> pTicketPatternList) { String methodName = "parseTicketNumberFromChangeLogs()"; Set<String> ticketNumberList = HashSet<String>(); String message = " Input ProjectName= ${pProjectName}, ChangeLogMessages=${pChangeLogMessages} , TicketPatternList=${pTicketPatternList}" log.logInfo(methodName, message) if("Gradle".equals(pProjectName)) { String[] patterns = ["ZZZ-"]; pTicketPatternList = Arrays.asList(patterns); message = "override pattern for Gradle TicketPatternList=${pTicketPatternList}" log.logInfo(methodName, message) } Collection<String> parsedTicketNumbers = flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList); message = "parsedTicketNumbers=${parsedTicketNumbers}" log.logInfo(methodName, message) if(parsedTicketNumbers != null && !parsedTicketNumbers.isEmpty() && pTicketPatternList != null && !pTicketPatternList.isEmpty()) { parsedTicketNumbers.each{ parsedTicket -> pTicketPatternList.each{ pattern -> if(parsedTicket.startsWith(pattern)) { String ticketNumber = parsedTicket.substring(pattern.length(),parsedTicket.length()) ticketNumberList.add(ticketNumber) } } } } return ticketNumberList; } } |
There are some utility variables provided by FlexDeploy that can used in your custom Groovy code.
putRequest(String pHostName, String pUserName, String pPassword, String pResourcePath, String pPayload) - Returns boolean
def builder = new groovy.json.JsonBuilder() def root = builder.issue { notes "${pComment}" } String payload = builder.toString(); String ticketNumber = ticket.getNumber(); String resourcePath = REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticketNumber) fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload); |