Versions Compared

Key

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

...

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 extend IssueTrackingSystem in order to be used by FlexDeploy. If you are not going to allow updating ticketscalling a method, 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. 

...

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

...

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

Example class RedmineServiceIntegration.java
Code Block
languagejava
package
.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.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.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
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 StringBuilder getURLBuilder()
  {
    StringBuilder urlBuilder = new StringBuilder((String) getProperties().get(FDREDMINE_URL));
    return urlBuilder;
  }


  private String getPropertyForTicket(String propertyName, String pTicketNummber)
  {
    String origPropValue = (String) getProperties().get(propertyName);
    if (origPropValue != null)
    {
      return origPropValue.replace(FDREDMINE_ISSUE, pTicketNummber);
    }
    else
    {
      return null;
    }
  }

  private String getURLProperty(String propertyName, String pTicketNummber)
  {
    String methodName = "getURLProperty";
    LOG.logFinestEntering(methodName);

    StringBuilder ticketURLBuilder = getURLBuilder();
    String urlProperty = ticketURLBuilder.append(getPropertyForTicket(propertyName, pTicketNummber)).toString();

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

  private String getHostNameURL()
  {
    return getURLBuilder().toString();
  }

  private 
StringBuilder
String 
getURLBuilder
getUserName()
  {
    
StringBuilder urlBuilder = new StringBuilder(
return (String) getProperties().get(FDREDMINE_USER_
URL)); return urlBuilder
NMAE);
  }


  private
String getPropertyForTicket(String propertyName,
 String 
pTicketNummber
getPassword()
  {
    
String
return 
origPropValue =
(String) getProperties().get(
propertyName
FDREDMINE_PASSWORD);
  }

  private 
if
FlexRESTClient getRestService(
origPropValue
)
!=
 
null)
   throws ApiException
{
  {
    return 
origPropValue.replace(FDREDMINE_ISSUE, pTicketNummber);
RestService.getService(getHostNameURL(), getUserName(), getPassword());
  }

  private FlexRESTClient 
}
getTicketWebResource(String pTicketNummber)
   
else
 throws ApiException
  {
    String ticketPath = getPropertyForTicket(FDREDMINE_TICKET_URL_PATTERN, pTicketNummber);
    return
null
 getRestService().path(ticketPath);
  }

}
  public 
}
String getWorkItemStatus(WorkItem ticket)
 
private
 
String
 
getURLProperty(String
 
propertyName,
throws 
String
ApiException
pTicketNummber)
  
{
    String methodName = "
getURLProperty
getTicketStatus";
    LOG.logFinestEntering(methodName, ticket.getNumber());

    FlexRESTClientResponse response;
    try
 
StringBuilder
  
ticketURLBuilder
 
=
{
getURLBuilder();
     
String
 
urlProperty
response = 
ticketURLBuilder
RestService.
append(getPropertyForTicket(propertyName, pTicketNummber)).toString();
checkResponse(getTicketWebResource(ticket.getNumber()).get());
    }
    catch (FlexCheckedException e)
    {
 
LOG.logFinestExiting(methodName,
 
urlProperty);
    throw 
return urlProperty;
new ApiException(e);
    }

    
private
JsonObject jsonObject 
String getHostNameURL()
= RestService.readJsonObject(response.getResponseString());
  
{
  JsonObject issueObject 
return
= 
getURLBuilder
(JsonObject) jsonObject.
toString
get("issue");
    String statusName = 
}
null;
   
private
 
String
if 
getUserName
(issueObject != null)
    {
      javax.json.JsonObject 
return
status 
(String) getProperties().get(FDREDMINE_USER_NMAE
= issueObject.getJsonObject("status");
  
}
    if 
private
(status 
String
!= 
getPassword(
null)
  
{
    {
return
 
(String) getProperties().get(FDREDMINE_PASSWORD);
   
}
    
private
statusName 
FlexRESTClient
= 
getRestService()
status.getString("name");
      }
throws

ApiException
   
{
 }
   
return
 
RestService
LOG.
getService
logFinestExiting(
getHostNameURL()
methodName, 
getUserName(), getPassword()
statusName);
  
}
  return statusName;
private
 
FlexRESTClient getTicketWebResource(String pTicketNummber) throws ApiException
 }

  
{
/*
   * 
String
Populates 
ticketPath
ticket's 
=
data 
getPropertyForTicket(FDREDMINE_TICKET_URL_PATTERN, pTicketNummber);
(e.g. Description with summary)
  
return getRestService().path(ticketPath);
 */
  
}
@Override
  public 
String
void 
getWorkItemStatus
populateWorkItem(WorkItem ticket)
    throws ApiException
  {
    String methodName = "
getTicketStatus
populateWorkItem";
    LOG.logFinestEntering(methodName, ticket
.getNumber(
)
)
;

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

    JsonObject jsonObject = RestService.readJsonObject(response.getResponseString());
    
JsonObject
if 
issueObject
(jsonObject !=
(JsonObject)
 null)
    {
      javax.json.JsonObject issue = jsonObject.
get
getJsonObject("issue")
; String statusName = null;
;
      
if (
issueObject
issue != null)
      {
        ticket.setDescription(issue.getString("description"));
        javax.json.JsonObject 
status
tracker = 
issueObject
issue.getJsonObject("
status
tracker");
        if (
status
tracker != null)
        {
        
statusName
 
=
 
status
ticket.setType(tracker.getString("name"));
        }
      }
    }
    LOG.logFinestExiting(methodName
, statusName
);
  
return statusName; }
}


  /*
   * Builds 
Populates
absolute ticket's 
data (e.g. Description with summary)
URL
   */
  @Override
  public 
void
String 
populateWorkItem
getWorkItemURL(WorkItem ticket)
    throws ApiException
  {
    String methodName = "
populateWorkItem
getWorkItemURL";
    LOG.logFinestEntering(methodName, ticket);

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

  /*
   
response
* 
= RestService.checkResponse(getTicketWebResource(ticket.getNumber()).get()); }
Adds a comment to a ticket
   *
 
catch
 
(FlexCheckedException
 
e)
*/
  @Override
 
{
 public void addCommentToWorkItem(WorkItem ticket, String comment)
throw
 
new
 
ApiException(e);
  throws ApiException
 
}
 {
    
JsonObject
String 
jsonObject
methodName = 
RestService.readJsonObject(response.getResponseString())
"addCommentToWorkItem";
    
if (jsonObject != null)
LOG.logFinestEntering(methodName, ticket.getNumber(), comment);
    
{
JsonObject commentJson = 
javax.json.JsonObject issue = jsonObject.getJsonObject("issue")
Json.createObjectBuilder().add("issue", Json.createObjectBuilder().add("notes", comment)).build();

    FlexRESTClientResponse 
if
response;
(issue
 
!=
 
null)
  try
    {
      response = RestService.checkResponse(getTicketWebResource(ticket
.setDescription(issue.getString("description"
.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(commentJson.toString()));
    }
   
javax.json.JsonObject
 
tracker
catch 
= issue.getJsonObject("tracker");
(FlexCheckedException e)
    {
     
if
 
(tracker
throw 
!= null)
new ApiException(e);
    }

  
{
  LOG.logFinestExiting(methodName);
  }

  
ticket.setType(tracker.getString("name"));
@SuppressWarnings("oracle.jdeveloper.java.insufficient-catch-block")
  private int getStatusIdFromName(String pStatus)
  
}
  throws ApiException
  {
}
    
}
String methodName = "getStatusIdFromName";
    LOG.
logFinestExiting
logFinestEntering(methodName, pStatus);

 
}
   int 
/* * Builds absolute ticket's URL */ @Override public String getWorkItemURL(WorkItem ticket) throws ApiException {
status_id = -1;
    try
    {
      String 
methodName
statusNumber = pStatus.split("
getWorkItemURL
##")[0];
      // If user saves 
LOG.logFinestEntering(methodName, ticket); String url = getURLProperty(FDREDMINE_TICKET_URL_PATTERN, ticket.getNumber());
10 or 10##In Progress, let's try to parse it as number
      
url
status_id = 
url.replace(".json", ""
Integer.parseInt(statusNumber);
    
LOG.logFinestExiting(methodName, url);
}
    catch (Exception nfe)
    
return
{
url;
   
}
   
//
*
 not integer value, 
*
call 
Adds
API
a
 
comment
 
to
 
a
 
ticket
}

  
*
  if 
*/
(status_id == -1)
 
@Override
   
public
{
void
 
addCommentToWorkItem(WorkItem
 
ticket,
 
String
 
comment)
  // call API 
throws
to 
ApiException
do conversion
 
{
     
String
try
methodName
 
=
 
"addCommentToWorkItem";
    {
LOG.logFinestEntering(methodName, ticket.getNumber(), comment);
        
JsonObject
FlexRESTClientResponse 
commentJson
response = 
Json
RestService.
createObjectBuilder
checkResponse(getRestService().
add
path("/issue
", Json.createObjectBuilder().add("notes", comment)).build();
_statuses.json").get());
        Map<String, Integer> statusMap = parseIssueStatuses(response.getResponseString());
       
FlexRESTClientResponse
 
response;
if (statusMap.containsKey(pStatus))
   
try
     {
      
response
  
= RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(commentJson.toString()));
  status_id = statusMap.get(pStatus);
        }
      }
      catch (
FlexCheckedException
ApiException 
e
apie)
      {
        throw apie;
new
 
ApiException(e);
     }
      catch 
LOG.logFinestExiting(methodName); }
(FlexCheckedException e)
      {
     
@SuppressWarnings("oracle.jdeveloper.java.insufficient-catch-block")
   
private
throw 
int
new 
getStatusIdFromName
ApiException(
String pStatus
e);
    
throws
 
ApiException
 }
 
{
   }

String
 
methodName
 
=
 
"getStatusIdFromName";
 
LOG.
logFinestEntering
logFinestExiting(methodName, 
pStatus
status_id);
    return 
int
status_id;
=
 
-1;
 }

  
try
private Map<String, Integer> parseIssueStatuses(String pJson)
{
  {
    String 
statusNumber
methodName = 
pStatus.split("##")[0];
"parseIssueStatuses";
    LOG.logFinestEntering(methodName, pJson);

    // Example output
If
  
user
 
saves
 
10
// 
or
 
10##In
 
Progress,
 
let's
{
try
 
to
 
parse
 
it
 
as
// 
number
       
status_id = Integer.parseInt(statusNumber);
"issue_statuses": [
    
}
//     
catch
 
(Exception
 
nfe)
     {
    
//
not
 
integer
 
value,
 
call
 
API
     
}
      
if
 
(status_id == -1)
"id": 1,
    
{
//       
//
 
call
 
API
 
to
 
do
 
conversion
    "name": "New",
 
try
   //    
{
         
FlexRESTClientResponse
 
response
 
=
 
RestService.checkResponse(getRestService().path("/issue_statuses.json").get());
"is_closed": false
    //            
Map<String
},
Integer> statusMap = parseIssueStatuses(response.getResponseString());

    //            
if (statusMap.containsKey(pStatus))
{
    //     
{
           
status_id = statusMap.get(pStatus);
"id": 2,
    //           
}
     "name": "In 
}
Progress",
    //  
catch
 
(ApiException
 
apie)
       
{
     "is_closed": false
  
throw
 
apie;
 //      
}
      },
catch
 
(FlexCheckedException
 
e)
  //     
{
       {
 
throw
 
new
 
ApiException(e);
 //      
}
     
}
     
LOG.logFinestExiting(methodName, status_id);
"id": 3,
    //  
return
 
status_id;
   
}
    
private
 
Map<String,
 
Integer>
 
parseIssueStatuses(String
 
pJson)
  
{
"name": "Resolved",
    
String
// 
methodName
 
=
 
"parseIssueStatuses";
     
LOG.logFinestEntering(methodName,
 
pJson);
      
// Example output
 "is_closed": false
    //    
{
     
//
   },
    
"issue_statuses": [
//            {
    //                "id": 
1
4,
    //                "name": "
New
Closed",
    //                "is_closed": 
false
true
    //            },
    //            {
    //                "id": 
2
5,
    //                "name": "
In Progress
Rejected",
    //                "is_closed": 
false
true
    //            },
    //            {
    //                "id": 
3
6,
    //                "name": "
Resolved
Ready",
    //                "is_closed": false
    //            }
,

    //        ]
    //    }

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

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

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


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

    // Redmine API needs id and not status name
    JsonObjectBuilder addStatus = 
"is_closed": true //
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()));
    
"id": 6,
}
    catch 
//
(FlexCheckedException e)
    {
      throw new 
"name": "Ready",
ApiException(e);
    }

//
    LOG.logFinestExiting(methodName);
  }

  @Override
  public 
"is_closed": false
void checkConnection()
    throws 
//
ApiException
  {
    String methodName = "checkConnection";
}
    
//
LOG.logFineEntering(methodName);

    
] //
try
    
}
{
     
Map<String,
 
Integer>
String 
map
aboutUrl = 
new HashMap<>()
"/users/current.json";
     
JsonObject jsonObject = RestService.readJsonObject(pJson); JsonArray ar = jsonObject.getJsonArray("issue_statuses"
 LOG.logInfo(methodName, "Checking Redmine connection for user {0} with path {1}", getUserName(), getHostNameURL() + aboutUrl);
    
for
  
(int
FlexRESTClientResponse 
i
clientResponse = 
0; i < ar.size
getRestService().path(aboutUrl).get();
i++)
     
{
 LOG.logInfo(methodName, "Successfully invoked test connection URL 
String name = ar.getJsonObject(i).getString("name"
{0}", clientResponse);
      int 
id
statusCode = 
ar.getJsonObject(i
clientResponse.getResponse().getStatusInfo().
getInt
getStatusCode(
"id"
);
      if 
map.put(name, id);
(statusCode == 401)
   
}
   
LOG.logFinestExiting(methodName, map);
{
  
return
 
map;
   
}
  throw 
/* * Changes ticket's status *
new ApiException("Invalid credentials.", "");
      }
     
*/
 RestService.checkResponse(clientResponse);
 
@Override
   
public
 
void
 
changeWorkItemStatusTo(WorkItem ticket
LOG.logInfo(methodName, 
Serializable
"Test 
status)
connection response code looks valid, 
throws
check 
ApiException
content of response");
{
     
String methodName = "changeWorkItemStatusTo";
 RestService.readJsonObject(clientResponse.getResponseString());
      LOG.
logFinestEntering
logInfo(methodName, 
ticket.getNumber(), status); // Redmine API needs id and not status name JsonObjectBuilder addStatus = Json.createObjectBuilder().add("notes", "Status updated to " + status);
"Validated that JSON data was received from test connction URL invocation.");
    }
    catch (ApiException e)
    {
     
addStatus
 
= addStatus
LOG.
add("status_id", getStatusIdFromName((String) status)); JsonObject statusJson = Json.createObjectBuilder().add("issue", addStatus).build();
logInfo(methodName, "ApiException in Test connection", e);
      
FlexRESTClientResponse
throw 
response
e;
    
try
}
    
{
catch (Exception e)
    
response
{
=
 
RestService.checkResponse(getTicketWebResource(ticket.getNumber()).mediatype(MediaType.APPLICATION_JSON_TYPE).put(statusJson.toString()));
     
} catch (FlexCheckedException
LOG.logInfo(methodName, "Failed in Test connection", e);
    
{
  throw new ApiException("Connection failed. " 
throw new ApiException(e
+ e.getMessage(), "");
    }
    
LOG.
logFinestExiting
logFineExiting(methodName);
  }

  @Override
  public 
void
Collection<String> 
checkConnection(
parseWorkItemNumberFromChangeLogs(List<String> pChangeLogMessages, List<String> pTicketPatternList)
    throws ApiException
  {
    String methodName = "
checkConnection
parseWorkItemNumberFromChangeLogs";
    LOG.
logFineEntering
logFinestEntering(methodName, pChangeLogMessages, pTicketPatternList);
    Collection<String> ticketNumberList = new ArrayList<String>();
 
try
   Collection<String> parsedTicketNumbers 
{
= flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList);
    
String aboutUrl = "/users/current.json";
if (FlexCommonUtils.isNotEmpty(parsedTicketNumbers) && FlexCommonUtils.isNotEmpty(pTicketPatternList))
    {
  
LOG.logInfo(methodName,
 
"Checking
 
Redmine
 
connection
 for (String 
user {0} with path {1}", getUserName(), getHostNameURL() + aboutUrl);
parsedTicketWithPattern: parsedTicketNumbers)
      {
      
FlexRESTClientResponse
 
clientResponse
 
=
for 
getRestService().path(aboutUrl).get();
(String pattern: pTicketPatternList)
  
LOG.logInfo(methodName,
 
"Successfully
 
invoked
 
test
 
connection
 
URL
 {
0}", clientResponse);

        
int
 
statusCode
 
=
if 
clientResponse.getResponse
(
)
parsedTicketWithPattern.
getStatusInfo
startsWith(pattern)
.getStatusCode(
)
;

      
if
 
(statusCode
 
==
 
401)
 {
     
{
       String ticketNumber 
throw new ApiException("Invalid credentials.", ""
= parsedTicketWithPattern.substring(pattern.length(), parsedTicketWithPattern.length());
      
}
      
RestService
ticketNumberList.
checkResponse
add(
clientResponse
ticketNumber);

  
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."
   }
    }
    LOG.logFinestExiting(methodName);
    
}
return ticketNumberList;
  }
catch

(ApiException
 
e)
 @Override
  public 
{
Collection<WorkItemComment> getWorkItemComments(WorkItem pTicket, int i, int 
LOG.logInfo(methodName, "ApiException in Test connection", e);
i2)
    throws ApiException
  {
   
throw
 
e;
// TODO Implement this method
}
    return 
catch (Exception e)
Collections.emptySet();
  }

{
  @Override
  
LOG.logInfo(methodName, "Failed in Test connection", e);
public Collection<WorkItemUpdate> getWorkItemHistory(WorkItem pTicket, int i, int i2)
    throws ApiException
throw
 
new
 
ApiException("Connection failed. " + e.getMessage(), ""); }
{
    // TODO Implement this method
    return 
LOG
Collections.
logFineExiting
emptySet(
methodName
);
  }

  @Override
  public 
Collection<String>
Collection<WorkItemAttachment> 
parseWorkItemNumberFromChangeLogs
getWorkItemAttachments(
List<String> pChangeLogMessages, List<String> pTicketPatternList
WorkItem pTicket)
    throws ApiException
  {
    
String methodName = "parseWorkItemNumberFromChangeLogs"; LOG.logFinestEntering(methodName, pChangeLogMessages, pTicketPatternList); Collection<String> ticketNumberList = new ArrayList<String>
// TODO Implement this method
    return Collections.emptySet();
  }

Collection<String>
 
parsedTicketNumbers
 
= flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList); if (FlexCommonUtils.isNotEmpty(parsedTicketNumbers) && FlexCommonUtils.isNotEmpty(pTicketPatternList))
@Override
  public WorkItemDetails getWorkItem(WorkItem pTicket)
    throws ApiException
  {
    // TODO 
for
Implement 
(String
this 
parsedTicketWithPattern:
method
parsedTicketNumbers)
    return null;
 
{
 }

  @Override
  public 
for
InputStream getWorkItemAttachmentContent(
String pattern: pTicketPatternList
WorkItem pTicket, Serializable pSerializable)
    throws ApiException
  {
    // TODO Implement this method
 
if
 
(parsedTicketWithPattern.startsWith(pattern))
  return null;
  }

  @Override
{
  public List<WorkItemDetails> getWorkItems(List<WorkItem> pList)
    throws ApiException
 
String
 
ticketNumber
{
=
 
parsedTicketWithPattern.substring(pattern.length(),
 
parsedTicketWithPattern.length());
  // TODO Implement this method
    return 
ticketNumberList
Collections.
add
emptyList(
ticketNumber
);
  }

  @Override
  public List<WorkItemStatus> getAvailableWorkItemStatuses(WorkItem pWorkItem)
 
}
   throws ApiException
  {
 
}
   // TODO Implement this 
}
method
    
}
return Collections.emptyList();
  
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
}
}
  • 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)
    {
    
//
 
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 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.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 = "
getTicketStatus
getWorkItemURL()";
    String message = "
ticket# ${ticket.toString()}"
    log.logInfo(methodName, message);
    String resourcePath = REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
	message = "Redmine ticket 
javax.json.JsonObject jsonObj = fdrestutils.getRequest(
URL is # ${REDMINE_URL}, resourcePath=${resourcePath}"
    log.logInfo(methodName,
REDMINE_USER_NMAE,
 message)
	return REDMINE_
PASSWORD)
URL + resourcePath;
  }
 
if(jsonObj
 
!=

null)
  void populateWorkItem(WorkItem ticket)
{
  {
    
javax.json.JsonObject issue
String methodName = 
jsonObj.getJsonObject
"populateWorkItem(
"issue")
)";
	String message = "Populate ticket# ${ticket.toString()}"
    
if(issue!=null)
log.logInfo(methodName, message)
    String resourcePath 
{
= REDMINE_TICKET_URL_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
    
javax.json.JsonObject 
status
jsonObj = 
issue
fdrestutils.
getJsonObject("status")
getRequest(REDMINE_URL, resourcePath, REDMINE_USER_NMAE, REDMINE_PASSWORD);
    if(
status
jsonObj != null)

    {
      javax.json.JsonObject issue = 
return status.getString
jsonObj.getJsonObject("
name
issue")
      
} } } return null;
if(issue!=null)
     
}
 {
      
String
 
getTicketURL(Ticket
 ticket.setDescription(issue.getString("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 = "
populateTicket
addCommentToWorkItem()";   
    try
    { 
	  String message = " 
=
Adding 
"Populate
comment 
ticket#
to ${ticket.toString()} , comment=${pComment}"
      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)
def builder = new groovy.json.JsonBuilder()
      def root = builder.issue {
     
{
       
javax.json.JsonObject
 
issue
 
=
notes 
jsonObj.getJsonObject("issue")
"${pComment}"
       }
   
if(issue!=null)
   String payload = builder.toString();
{
      String resourcePath 
ticket.setDescription(issue.getString("description"))
= REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
      fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, 
javax.json.JsonObject tracker = issue.getJsonObject("tracker")
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 to ${pStatus}"
		status_id "${pStatus}"
	}
	String payload = 
def root
builder.toString();
	String resourcePath = 
builder.issue {
REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
	fdrestutils.putRequest(REDMINE_URL, REDMINE_USER_NMAE, REDMINE_PASSWORD, resourcePath, payload);
  }
   
 
notes "${pComment}"
 def parseWorkItemNumberFromChangeLogs(List<String> pChangeLogMessages, List<String> pTicketPatternList)
  {
}
	String methodName = "parseWorkItemNumberFromChangeLogs()";
String payload
	Set<String> ticketNumberList = 
builder.toString
HashSet<String>();
	String message = " Input 
String resourcePath = REDMINE_TICKET_REST_PATTERN.replaceAll("\\{REDMINE_ISSUE\\}", ticket.getNumber())
ChangeLogMessages=${pChangeLogMessages} , TicketPatternList=${pTicketPatternList}"
    log.logInfo(methodName, message)	
    
    
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;
  }
  
  public Collection<WorkItemComment> getWorkItemComments(WorkItem pTicket, int i, int i2)
    throws ApiException
  {
    return Collections.emptySet();
  }
  
  public Collection<WorkItemUpdate> getWorkItemHistory(WorkItem pTicket, int i, int i2)
    throws ApiException
  {
    // TODO Implement this method
    return Collections.emptySet();
  }

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

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

  public 
Collection<String>
InputStream 
parsedTicketNumbers = flexagon.fd.model.integration.its.util.ChangeLogParser.getParser().parse(pChangeLogMessages, pTicketPatternList); message = "parsedTicketNumbers=${parsedTicketNumbers}"
getWorkItemAttachmentContent(WorkItem pTicket, Serializable pSerializable)
    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
  {
    // TODO Implement this method
parsedTicketNumbers.each{
 
parsedTicket
 
->
  return Collections.emptyList();
  }

 
pTicketPatternList.each{
 
pattern
public 
-> if(parsedTicket.startsWith(pattern)) { String ticketNumber = parsedTicket.substring(pattern.length(),parsedTicket.length()) ticketNumberList.add(ticketNumber) } } } } return ticketNumberList
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.", "");

...