Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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

...

Let's look at more details on how to create a custom issue tracking system integration with Flex DeployIssue 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.

  • Click Save.

...

  • 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 creating editing a custom issue tracking system with properties. You can add more as necessaryexisting properties using the drop down on the create button.

...

API Implementation

Your 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, 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. 

...

extend IssueTrackingSystem in order to be used by FlexDeploy. If you are not going to allow calling a method, you can leave the body blank, or throw an error in the body, but the method must be defined. If you choose to make a Java implementation, your IDE will verify that you have implemented all the required methods. If you choose the Groovy implementation, then the FlexDeploy UI will validate that your class contains the necessary methods. Groovy implementations can be used even with 3rd party jars that you place in libext, and allow for easier iterations. The advantage of a Java implementation is a potentially tighter integration with source control.

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.

Java Example

Expand
titleExample class RedmineServiceIntegration.java
Code Block
languagejava
package flexagon.fd.model.integration.its.impl.redmine;

import flexagon.fd.model.integration.its.api.IssueTrackingSystem;
import flexagon.fd.model.integration.its.api.WorkItem;
import flexagon.fd.model.integration.its.api.

...

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

Example class RedmineServiceIntegration.java
Code Block
languagejava
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.", "");
WorkItemAttachment;
import flexagon.fd.model.integration.its.api.WorkItemComment;
import flexagon.fd.model.integration.its.api.WorkItemDetails;
import flexagon.fd.model.integration.its.api.WorkItemStatus;
import flexagon.fd.model.integration.its.api.WorkItemUpdate;
import flexagon.fd.model.integration.util.ApiException;
import flexagon.fd.model.integration.util.RestService;

import flexagon.ff.common.core.exceptions.FlexCheckedException;
import flexagon.ff.common.core.logging.FlexLogger;
import flexagon.ff.common.core.rest.FlexRESTClient;
import flexagon.ff.common.core.rest.FlexRESTClientResponse;
import flexagon.ff.common.core.utils.FlexCommonUtils;

import java.io.InputStream;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

import javax.ws.rs.core.MediaType;

public class RedmineIssueTrackingSystem
  extends IssueTrackingSystem
{
  private static final String CLZ_NAM = RedmineIssueTrackingSystem.class.getName();
  private static final FlexLogger LOG = FlexLogger.getLogger(CLZ_NAM);

  private static final String FDREDMINE_URL = "FDREDMINE_URL";
  private static final String FDREDMINE_TICKET_URL_PATTERN = "FDREDMINE_TICKET_URL_PATTERN";
  private static final String FDREDMINE_ISSUE = "{REDMINE_ISSUE}";
  private static final String FDREDMINE_USER_NMAE = "FDREDMINE_USER_NMAE";
  private static final String FDREDMINE_PASSWORD = "FDREDMINE_PASSWORD";

  public RedmineIssueTrackingSystem()
  {
    super();
  }

  private 
if
StringBuilder getURLBuilder(
!(clientResponse.getStatusInfo(
)
.getFamily() ==

Response.Status.Family.SUCCESSFUL))
  
{
    StringBuilder urlBuilder 
throw
= new 
ApiException
StringBuilder(
clientResponse.getEntity
(String
.class
)
,
 
clientResponse.getStatusInfo
getProperties().
getReasonPhrase
get(FDREDMINE_URL));
    
}
return urlBuilder;
  }
System.out.println(methodName

+

"
 
Test
 
connection
private 
response
String 
code looks valid, check content of response");
getPropertyForTicket(String propertyName, String pTicketNummber)
  {
    
System.out.println(methodName + " Validated that JSON data was received from test connection URL invocation."); } private StringBuilder getRedmineURLBuilder()
String origPropValue = (String) getProperties().get(propertyName);
    if (origPropValue != null)
    {
      return origPropValue.replace(FDREDMINE_ISSUE, pTicketNummber);
    }
    else
    {
    
StringBuilder
  
urlBuilder
return 
=
null;
new
 
StringBuilder((String)
 
getProperties().get("REDMINE_URL"));
  }
  
return
}
urlBuilder;

  
}
private String getURLProperty(String propertyName, 
private
String 
getRedmineURL(
pTicketNummber)
  {
    
return getRedmineURLBuilder().toString(); }
String methodName = "getURLProperty";
    
private String getUserName()
LOG.logFinestEntering(methodName);

 
{
   StringBuilder ticketURLBuilder 
return (String) getProperties().get("REDMINE_USER_NMAE")
= getURLBuilder();
  
}
  String urlProperty 
private String getPassword()
= ticketURLBuilder.append(getPropertyForTicket(propertyName, pTicketNummber)).toString();

 
{
   
return (String) getProperties().get("REDMINE_PASSWORD")
LOG.logFinestExiting(methodName, urlProperty);
  
}
  return urlProperty;
  }
@Override

  
public
private 
void
String 
populateTicket
getHostNameURL(
Ticket pTicket
)
  {
 
throws
 
ApiException
  
{
return getURLBuilder().toString();
  }

//
 
TODO
 
Implement
private 
this
String 
method
getUserName()
  
}
{
   
@Override
 return 
public
(String) 
getTicketURL(Ticket pTicket)
getProperties().get(FDREDMINE_USER_NMAE);
  }

throws
 
ApiException
 private String 
{
getPassword()
  {
 
//
 
TODO
 
Implement
 
this
return 
method return null
(String) getProperties().get(FDREDMINE_PASSWORD);
  }

  
@Override
private 
public void addCommentToTicket(Ticket pTicket, String pString
FlexRESTClient getRestService()
    throws ApiException
  {
    
// TODO Implement this method
return RestService.getService(getHostNameURL(), getUserName(), getPassword());
  }

  
@Override
private 
public void changeTicketStatusTo(Ticket pTicket, String pString
FlexRESTClient getTicketWebResource(String pTicketNummber)
    throws ApiException
  {
    String ticketPath 
// TODO Implement this method } @Override
= getPropertyForTicket(FDREDMINE_TICKET_URL_PATTERN, pTicketNummber);
    return getRestService().path(ticketPath);
  }

  public String 
getTicketStatus
getWorkItemStatus(
Ticket
WorkItem 
pTicket
ticket)
    throws ApiException
  {
    
//
String 
TODO
methodName 
Implement this method
= "getTicketStatus";
    
return null
LOG.logFinestEntering(methodName, ticket.getNumber());

 
}
   FlexRESTClientResponse 
private
response;
Client
 
getRestClient()
   
{
try
    
Client
{
restClient
 
=
 
Client.create();
    response 
restClient
= RestService.
addFilter
checkResponse(
new HTTPBasicAuthFilter(getUserName(), getPassword()
getTicketWebResource(ticket.getNumber()).get());
    }
restClient.setReadTimeout(20000);
    catch 
restClient.setConnectTimeout(10000);
(FlexCheckedException e)
    
return
{
 
restClient;
   
}
  throw new 
public WebResource getWebResource(String resource)
ApiException(e);
    
throws
}
ApiException

  
{
  JsonObject jsonObject 
String methodName = "getWebResource"
= RestService.readJsonObject(response.getResponseString());
    JsonObject 
if
issueObject 
(resource !
= 
null && !resource.isEmpty() && !resource.startsWith("/"))
(JsonObject) jsonObject.get("issue");
    
{
String statusName = null;
   
resource += "/" + resource;
 if (issueObject != null)
    {
 
}
     
WebResource webResource
javax.json.JsonObject status = 
null;
issueObject.getJsonObject("status");
     
try
 if (status != null)
      {
      
webResource
  statusName = 
getRestClient()
status.
resource(getRedmineURL()).path(resource
getString("name");
      }

    }
   
catch
 LOG.logFinestExiting(
Exception
methodName, 
e
statusName);
    
{
return statusName;
  }

 
throw new ApiException(e.getMessage(), e.getMessage()); }
 /*
   * Populates ticket's data (e.g. Description with summary)
   */
  @Override
  public void populateWorkItem(WorkItem ticket)
    throws ApiException
  {
    String methodName 
return
= 
webResource
"populateWorkItem";
  
} }
  • 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.

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
Code Block
languagegroovy
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
  LOG.logFinestEntering(methodName, ticket);

    FlexRESTClientResponse response;
    try
    {
      response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).get());
    }
    catch (FlexCheckedException e)
    {
      throw new ApiException(e);
    }

    JsonObject jsonObject = RestService.readJsonObject(response.getResponseString());
    if (jsonObject != null)
    {
      javax.json.JsonObject issue = jsonObject.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"));
        }
      }
    }
    LOG.logFinestExiting(methodName);
  }


  /*
   * Builds absolute ticket's URL
   */
  @Override
  public String getWorkItemURL(WorkItem ticket)
    throws ApiException
  {
    String methodName = "getWorkItemURL";
    LOG.logFinestEntering(methodName, ticket);

    String url = getURLProperty(FDREDMINE_TICKET_URL_PATTERN, ticket.getNumber());
    url = url.replace(".json", "");
    LOG.logFinestExiting(methodName, url);
    return url;
  }

  /*
   * Adds a comment to a ticket
   *
   */
  @Override
  public void addCommentToWorkItem(WorkItem ticket, String comment)
    throws ApiException
  {
    String methodName = "addCommentToWorkItem";
    LOG.logFinestEntering(methodName, ticket.getNumber(), comment);
    JsonObject commentJson = Json.createObjectBuilder().add("issue", Json.createObjectBuilder().add("notes", comment)).build();

    FlexRESTClientResponse response;
    try
    {
      response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(commentJson.toString()));
    }
    catch (FlexCheckedException e)
    {
      throw new ApiException(e);
    }

    LOG.logFinestExiting(methodName);
  }

  @SuppressWarnings("oracle.jdeveloper.java.insufficient-catch-block")
  private int getStatusIdFromName(String pStatus)
    throws ApiException
  {
    String methodName = "getStatusIdFromName";
    LOG.logFinestEntering(methodName, pStatus);

    int status_id = -1;
    try
    {
      String statusNumber = pStatus.split("##")[0];
      // If user saves 10 or 10##In Progress, let's try to parse it as number
      status_id = Integer.parseInt(statusNumber);
    }
    catch (Exception nfe)
    {
      // not integer value, call API
    }

    if (status_id == -1)
    {
      // call API to do conversion
      try
      {
        FlexRESTClientResponse response = RestService.checkResponse(getRestService().path("/issue_statuses.json").get());
        Map<String, Integer> statusMap = parseIssueStatuses(response.getResponseString());
        if (statusMap.containsKey(pStatus))
        {
          status_id = statusMap.get(pStatus);
        }
      }
      catch (ApiException apie)
      {
        throw apie;
      }
      catch (FlexCheckedException e)
      {
        throw new ApiException(e);
      }
    }

    LOG.logFinestExiting(methodName, status_id);
    return status_id;
  }

  private Map<String, Integer> parseIssueStatuses(String pJson)
  {
    String methodName = "parseIssueStatuses";
    LOG.logFinestEntering(methodName, pJson);

    // Example output
    //    {
    //        "issue_statuses": [
    //            {
    //                "id": 1,
    //                "name": "New",
    //                "is_closed": false
    //            },
    //            {
    //                "id": 2,
    //                "name": "In Progress",
    //                "is_closed": false
    //            },
    //            {
    //                "id": 3,
    //                "name": "Resolved",
    //                "is_closed": false
    //            },
    //            {
    //                "id": 4,
    //                "name": "Closed",
    //                "is_closed": true
    //            },
    //            {
    //                "id": 5,
    //                "name": "Rejected",
    //                "is_closed": true
    //            },
    //            {
    //                "id": 6,
    //                "name": "Ready",
    //                "is_closed": false
    //            }
    //        ]
    //    }

    Map<String, Integer> map = new HashMap<>();

    JsonObject jsonObject = RestService.readJsonObject(pJson);
    JsonArray ar = jsonObject.getJsonArray("issue_statuses");
    for (int i = 0; i < ar.size(); i++)
    {
      String name = ar.getJsonObject(i).getString("name");
      int id = ar.getJsonObject(i).getInt("id");
      map.put(name, id);
    }

    LOG.logFinestExiting(methodName, map);
    return map;
  }

  /*
   * Changes ticket's status
   *
   */
  @Override
  public void changeWorkItemStatusTo(WorkItem ticket, Serializable status)
    throws ApiException
  {
    String methodName = "changeWorkItemStatusTo";
    LOG.logFinestEntering(methodName, ticket.getNumber(), status);

    // Redmine API needs id and not status name
    JsonObjectBuilder addStatus = Json.createObjectBuilder().add("notes", "Status updated to " + status);
    addStatus = addStatus.add("status_id", getStatusIdFromName((String) status));
    JsonObject statusJson = Json.createObjectBuilder().add("issue", addStatus).build();

    FlexRESTClientResponse response;
    try
    {
      response = RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(statusJson.toString()));
    }
    catch (FlexCheckedException e)
    {
      throw new ApiException(e);
    }

    LOG.logFinestExiting(methodName);
  }

  @Override
  public void checkConnection()
    throws ApiException
  {
    String methodName = "checkConnection";
    LOG.logFineEntering(methodName);

    try
    {
      String aboutUrl = "/users/current.json";
      LOG.logInfo(methodName, "Checking Redmine connection for user {0} with path {1}", getUserName(), getHostNameURL() + aboutUrl);
      FlexRESTClientResponse clientResponse = getRestService().path(aboutUrl).get();
      LOG.logInfo(methodName, "Successfully invoked test connection URL {0}", clientResponse);
      int statusCode = clientResponse.getResponse().getStatusInfo().getStatusCode();
      if (statusCode == 401)
      {
        throw new ApiException("Invalid credentials.", "");
      }
      RestService.checkResponse(clientResponse);
      LOG.logInfo(methodName, "Test connection response code looks valid, check content of response");
      RestService.readJsonObject(clientResponse.getResponseString());
      LOG.logInfo(methodName, "Validated that JSON data was received from test connction URL invocation.");
    }
    catch (ApiException e)
    {
      LOG.logInfo(methodName, "ApiException in Test connection", e);
      throw e;
    }
    catch (Exception e)
    {
      LOG.logInfo(methodName, "Failed in Test connection", e);
      throw new ApiException("Connection failed. " + e.getMessage(), "");
    }
    LOG.logFineExiting(methodName);
  }

  @Override
  public Collection<String> parseWorkItemNumberFromChangeLogs(List<String> pChangeLogMessages, List<String> pTicketPatternList)
    throws ApiException
  {
    String methodName = "parseWorkItemNumberFromChangeLogs";
    LOG.logFinestEntering(methodName, pChangeLogMessages, pTicketPatternList);
    Collection<String> ticketNumberList = new ArrayList<String>();
    Collection<String> parsedTicketNumbers = flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList);
    if (FlexCommonUtils.isNotEmpty(parsedTicketNumbers) && FlexCommonUtils.isNotEmpty(pTicketPatternList))
    {
      for (String parsedTicketWithPattern: parsedTicketNumbers)
      {
        for (String pattern: pTicketPatternList)
        {
          if (parsedTicketWithPattern.startsWith(pattern))
          {
            String ticketNumber = parsedTicketWithPattern.substring(pattern.length(), parsedTicketWithPattern.length());
            ticketNumberList.add(ticketNumber);
          }
        }
      }
    }
    LOG.logFinestExiting(methodName);
    return ticketNumberList;
  }

  @Override
  public Collection<WorkItemComment> getWorkItemComments(WorkItem pTicket, int i, int i2)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptySet();
  }

  @Override
  public Collection<WorkItemUpdate> getWorkItemHistory(WorkItem pTicket, int i, int i2)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptySet();
  }

  @Override
  public Collection<WorkItemAttachment> getWorkItemAttachments(WorkItem pTicket)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptySet();
  }

  @Override
  public WorkItemDetails getWorkItem(WorkItem pTicket)
    throws ApiException
  {
    // TODO Implement this method
    return null;
  }

  @Override
  public InputStream getWorkItemAttachmentContent(WorkItem pTicket, Serializable pSerializable)
    throws ApiException
  {
    // TODO Implement this method
    return null;
  }

  @Override
  public List<WorkItemDetails> getWorkItems(List<WorkItem> pList)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptyList();
  }

  @Override
  public List<WorkItemStatus> getAvailableWorkItemStatuses(WorkItem pWorkItem)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptyList();
  }
}
  • In order to compile your java class, you will need FlexDeployAPI.jar on classpath.

  • Implement all the methods defined by https://flexagon.com/function-sdk/6.5/flexagon/fd/model/integration/its/api/IssueTrackingSystem.html.

  • 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.

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.

Groovy Example
Expand
titleExample groovy RedmineIssueTrackingSystem.groovy
Code Block
languagegroovy
import flexagon.fd.model.integration.its.api.WorkItem;
import flexagon.fd.model.integration.its.api.WorkItemAttachment;
import flexagon.fd.model.integration.its.api.WorkItemComment;
import flexagon.fd.model.integration.its.api.WorkItemDetails;
import flexagon.fd.model.integration.its.api.WorkItemStatus;
import flexagon.fd.model.integration.its.api.WorkItemUpdate;
import flexagon.fd.model.integration.util.ApiException; 

class RedmineIssueTrackingSystem extends IssueTrackingSystem
{
  void 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 getWorkItemStatus(WorkItem ticket)
  {
    String methodName = "getWorkItemStatus()";
    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 getWorkItemURL(WorkItem ticket)
  {
    String methodName = "getWorkItemURL()";
    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;
  }
  
  void populateWorkItem(WorkItem ticket)
  {
    String methodName = "
getTicketStatus
populateWorkItem()";
	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)
!= null)
    {

      
javax.json.JsonObject 
status
issue = 
issue
jsonObj.getJsonObject("
status
issue")

      
if(
status
issue!=null)
      
{
        
return status
ticket.setDescription(issue.getString("
name") } } } return null; } String getTicketURL(Ticket ticket
description"))
  
{
      
String methodName
javax.json.JsonObject tracker = 
"getTicketURL()";
issue.getJsonObject("tracker")
    
String
 
message
 
=
 
"ticket# ${ticket.toString()}"
 if(tracker !=null)
        {
 
log.logInfo(methodName,
 
message);
     
String
 
resourcePath
 
= REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber()) message = "Redmine ticket URL is # ${REDMINE_URL}, resourcePath=${resourcePath}"
 ticket.setType(tracker.getString("name"))
        }       
      
log.logInfo(methodName, message) return REDMINE_URL + resourcePath;
}
    }
  }
  
  
def
void 
populateTicket
addCommentToWorkItem(
Ticket
WorkItem ticket, String pComment)
  {
	String methodName = "addCommentToWorkItem()";  
String methodName = "populateTicket()";
 
    try
    { 
	  String message = "
Populate ticket#
 Adding comment to ${ticket.toString()} , comment=${pComment}"
      log.logInfo(methodName, message)
    
String
  def 
resourcePath
builder = 
REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
new groovy.json.JsonBuilder()
      
javax.json.JsonObject jsonObj
def root = 
fdrestutils.getRequest(REDMINE_URL, resourcePath, REDMINE_USER_NMAE, REDMINE_PASSWORD);
builder.issue {
       
if(jsonObj
 
!=
 
null)
     notes "${pComment}"
      
javax.json.JsonObject issue = jsonObj.getJsonObject("issue")
 }
      String payload = 
if(issue!=null)
builder.toString();
      
{
String resourcePath 
= REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.
setDescription(issue.getString("description"
getNumber())
      fdrestutils.putRequest(REDMINE_URL, 
javax.json.JsonObject tracker = issue.getJsonObject("tracker")
REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload);
    }
    catch 
if
(
tracker
Exception 
!=null
e)
    {
   
{
   log.logInfo( methodName, " Failed while adding comment 
ticket.setType(tracker.getString("name")) }
to the issue - " + e.getMessage() + " " + e)
      throw new ApiException("Connection failed. " 
}
+ e.getMessage());
    }
  }
  
  
def
void 
addCommentToTicket
changeWorkItemStatusTo(
Ticket
WorkItem ticket, 
String
Serializable 
pComment
pStatus)
  {
    String methodName = "
addCommentToTicket
changeWorkItemStatusTo()";
try {

	
String message = " Adding comment to ${ticket.toString()} , 
comment
status=${
pComment
pStatus}"
    
log.logInfo(methodName, message)
	def builder = new groovy.json.JsonBuilder()
	def root = builder.issue {
		notes "Status updated 
def root = builder.issue {
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 parseWorkItemNumberFromChangeLogs(List<String> 
notes "${pComment}"
pChangeLogMessages, List<String> pTicketPatternList)
  {
	String methodName 
} String payload = builder.toString
= "parseWorkItemNumberFromChangeLogs()";
	Set<String> ticketNumberList = HashSet<String>();
	String message = " Input 
String resourcePath = REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
ChangeLogMessages=${pChangeLogMessages} , TicketPatternList=${pTicketPatternList}"
    
fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload);
log.logInfo(methodName, message)	
    
   
}
 Collection<String> parsedTicketNumbers 
catch (Exception e) {
= flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList);
	message = "parsedTicketNumbers=${parsedTicketNumbers}"
    log.logInfo(
methodName, 
"
message)
Failed
		
while
 
adding
 
comment
 
to
 
the issue - " + e.getMessage
if(parsedTicketNumbers != null && !parsedTicketNumbers.isEmpty() 
+
&& 
" " + e) throw new ApiException("Connection failed. " + e.getMessage());
pTicketPatternList != null && !pTicketPatternList.isEmpty())
    
}
{
  
}
    parsedTicketNumbers.each{ parsedTicket 
def changeTicketStatusTo(Ticket ticket, String pStatus)
->
        pTicketPatternList.each{ pattern ->
			if(parsedTicket.startsWith(pattern))
			{
				String 
methodName
ticketNumber =
"changeTicketStatusTo()"; String message = " Adding comment to ${ticket.toString()} , status=${pStatus}"
 parsedTicket.substring(pattern.length(),parsedTicket.length())
				ticketNumberList.add(ticketNumber)
			}
		}
      }
    
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
}
	return ticketNumberList;
  }
  
  public Collection<WorkItemComment> getWorkItemComments(WorkItem pTicket, int i, int i2)
    throws ApiException
  {
    return Collections.emptySet();
  }
  
  public 
def
Collection<WorkItemUpdate> 
parseTicketNumberFromChangeLogs
getWorkItemHistory(
String
WorkItem 
pProjectName
pTicket, 
List<String>
int 
pChangeLogMessages
i, 
List<String>
int 
pTicketPatternList
i2)
    throws ApiException
  {
String
 
methodName
 
=
 
"parseTicketNumberFromChangeLogs()"; Set<String> ticketNumberList = HashSet<String>(); String message = " Input ProjectName= ${pProjectName}, ChangeLogMessages=${pChangeLogMessages} , TicketPatternList=${pTicketPatternList}"
 // TODO Implement this method
    return Collections.emptySet();
  }

  
log.logInfo(methodName, message)
public Collection<WorkItemAttachment> getWorkItemAttachments(WorkItem pTicket)
    
if("Gradle".equals(pProjectName))
throws ApiException
  {
    // TODO Implement this method
String[]
   
patterns
 
= ["ZZZ-"]
return Collections.emptySet();
  }

  
pTicketPatternList
public 
=
WorkItemDetails 
Arrays.asList(patterns);
getWorkItem(WorkItem pTicket)
    throws ApiException
  {
    
message
// 
=
TODO 
"override
Implement 
pattern
this 
for
method
Gradle
 
TicketPatternList=${pTicketPatternList}"
   return null;
  
log.logInfo(methodName, message)
}

  public InputStream 
}
getWorkItemAttachmentContent(WorkItem pTicket, Serializable pSerializable)
 
Collection<String>
 
parsedTicketNumbers
 
= flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList); message = "parsedTicketNumbers=${parsedTicketNumbers}"
 throws ApiException
  {
    
log.logInfo(methodName, message)
// TODO Implement this method
   
if(parsedTicketNumbers
 
!=
return null;
&& !parsedTicketNumbers.isEmpty() && pTicketPatternList != null && !pTicketPatternList.isEmpty())
  }

  public List<WorkItemDetails> getWorkItems(List<WorkItem> pList)
    
{
throws ApiException
  {
  
parsedTicketNumbers.each{
 
parsedTicket
 
->
// TODO Implement this method
    
pTicketPatternList.each{ pattern -> if(parsedTicket.startsWith(pattern)) { String ticketNumber = parsedTicket.substring(pattern.length(),parsedTicket.length()) ticketNumberList.add(ticketNumber) } } } } return ticketNumberList
return Collections.emptyList();
  }

  public List<WorkItemStatus> getAvailableWorkItemStatuses(WorkItem pWorkItem)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptyList();
  }

}
  • 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.", "");

...