Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

FlexDeploy has out-of-box integrations with Jira, Redmine, Azure Boards, GitLab, and GitHub. You can easily integrate with other issue tracking systems as well. Such third-party Issue Tracking System integrations can be enabled using Java or Groovy implementation. Several ITS templates are shipped with FlexDeploy, but you can create more to connect to other systems that aren’t supported with a vanilla install.

Go to Issue Tracking Systems page using Configuration Integrations Issue Tracking menu.

Let's look at more details on how to create a custom Issue Tracking System Provider with FlexDeploy. Click Create to implement an integration with custom issue tracking system.

Go to the ITS Providers screen.

Click create, or click on an existing row if you are making changes to an existing ITS Provider.

  • Provide a unique name and optionally description

  • Define properties for the new issue tracking system. Properties are configuration values used by FlexDeploy to connect to the new system. The values of the properties that you define here are set on Issue Tracking Instances, folders, and projects.

    • If you define properties, you can indicate display and validation details. You can also indicate if a property is required and/or encrypted.

    • Enter a unique Name before adding any properties

  • Provide either Java Implementation or Groovy API. If both are supplied, the Java Implementation is used. Java implementation means that you are giving the classpath for a java class which you are placing the jar for in the libext folder. Groovy is defined on the Groovy Api tab. A Groovy script would allow for dynamic update, whereas use of Java code will require restart of server.

  • Click Save.

Here we are editing a custom issue tracking system with properties. You can add existing properties using the drop down on the create button.

API Implementation

  • Complete this section.

Your Implementation must include the following methods in order to be used by FlexDeploy. If you are not going to allow updating tickets, you can leave the body blank, or throw an error in the body, but the method must be defined.

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, which is used to link from FlexDeploy. (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. The status passed into the function is the status id.

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 and FlexDeploy can connect with the provided credentials

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

Java Implementation

Here are the high level steps for a Java implementation. You can use any IDE to prepare this implementation. 

  • Create java class that extends flexagon.fd.model.integration.its.api.IssueTrackingSystem. Example shown below has testConnection method implemented which uses properties map to retrieve the configuration values to connect to the issue tracking system.

  • All properties defined are available in Map returned by getProperties method.

Example class RedmineServiceIntegration.java
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 connection 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;
  }
}

  • In order to compile your java class, you will need FlexDeployAPI.jar on classpath.

  • Implement all the methods described in the table in the API Implementation section.

  • For any failure connecting to the system or if any issues with the data, then you can throw exception. For example throw new ApiException("Invalid credentials.", "");

  • Once you are ready with unit testing, you can prepare Jar file for your credential store java class and other utility classes. This jar file can be placed on server classpath now.

    • Put this jar file in apache-tomcat-flexdeploy/libext folder.

    • If you are using any third party libraries from your Java implementation, then those jar files will also need to be added to same libext folder.

  • Update this jar file

Groovy Implementation

Here are the high level steps for a 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.

  • Create a groovy class. Example shown below has testConnection and other methods implemented.

  • All properties defined are available as groovy binding variables. For example, properties can be accessed directly like REDMINE_URL, REDMINE_USER_NAME, REDMINE_PASSWORD, etc,

Example groovy RedmineIssueTrackingSystem.groovy
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;
  }

}
  • Implement all the methods described in the table in the API Implementation section

  • For any failure connecting to the system or if any issues with the data, then you can throw exception. For example, throw new ApiException("Invalid credentials.", "");

Groovy Utilities

There are some utility variables provided by FlexDeploy that can be used in your custom Groovy code.

  • log is a FlexDeploy logger variable which should be used to log any information from the groovy class.

  • itsstatusmap is a map<id, name> which can be used for getting the ITS status names from status ids. This can be useful for getting and updating ticket statuses if the API for your custom issue tracking system uses status names only.

  • fdrestutils is a utility object available to use FlexDeploy API to invoke any REST API. See the Java docs for more details on the functions available.

    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)

  • No labels