Integrate Jira Information into CodeScene’s Analyses

CodeScene’s Jira integration is optional, but highly recommended given that you have the information required for the analyses:

  • The Jira issue numbers are included/referenced in the commit messages.
  • You use lables and/or issue types in Jira to distinguish different kinds of work (e.g. Bugs, Features).

When present, the CodeScene integration lets you measure:

  • Accumulated costs per hotspot and sub-system.
  • Trends in the type of work you do, such as “Planned” versus “Unplanned” work.

The use cases for the Jira integration are described in Project Management Analyses.

Install the Jira Service

This Jira integration is provided by a separate service that you deploy independently of CodeScene. You should have received a download link for the Jira integration service in your license email from Empear.

When run in Tomcat, you need to specify the required JNDI context paths in the file conf/Context.xml in Tomcat 7. Here’s an example configuration that you want to add to conf/Context.xml:

<Environment name="codescene/enterprise/pm/jira/config" value="/mydocs/codescene/codescene-jira.yml" type="java.lang.String"/>

<Environment name="codescene/enterprise/pm/jira/dbpath" value="/mydocs/codescene/db/codescene-enterprise-pm-jira" type="java.lang.String"/>

The application uses the paths above to resolve both the configuration file and the internal database for JIRA synchronization data.

Configure the Jira Service to access Jira and be accessed by CodeScene

All configuration is done via yaml file. That file has to be referenced in the Environment – if you run Tomcat as described above – or present in a local file in the same directory as the Jira integration service if you execute the JAR file.

The configuration file path is resolved in the following order:

  1. Environment variable CODESCENE_JIRA_CONFIG, if set.
  2. JNDI context path codescene/enterprise/pm/jira/config, if set. Can be configured in Tomcat 7 in conf/context.xml, like this:
<Environment name="codescene/enterprise/pm/jira/config"
  value="/etc/codescene/codescene-jira.yml"
  type="java.lang.String"/>
  1. The file codescene-jira.yml in the current working directory.

If the configuration path doesn’t point to a valid YAML file the service fails to start.

sync:
  hour-interval: {number}
auth:
  service:
    username: {string}
    password: {string}
  jira:
    base-uri: {uri}
    username: {string}
    password: {string}
    rest-api-path: {string: optional, defaults to "/rest/api/latest"}
    http-timeout: {integer: optional, defaults to 10000}
projects:
  - key: {jira-project-key}
    cost-unit:
      type: {minutes|points}
      format: #optional
        singular: {format-string}
        plural: {format-string}
    cost-field: {jira-field-name}
    supported-work-types: [{jira-label}]
    rename-work-types: [{jira-label} => {codescene-label}]
    defect-and-failure-labels: [{jira-label}]
    work-in-progress-transition-name: {jira-status-name}
    ticket-id-pattern: {regex-pattern}

The jira username and password settings specify the credentials for connecting to Jira over using basic athentication. The password should be set to an atlassian API token and the username to the email address for the Atlassian account used to create the token. Note that basic authentication using user credentials/passwords is deprecated and will be removed.

The supported-work-types specify the JIRA labels and/or JIRA Issue Types you want to include in the analysis. Please note that only types with the listed labels/type will be included in the analysis.

The defect-and-failure-labels specify the JIRA labels and/or JIRA Issue Types that will be regarded as defects. Note that this is independent from the work-types configuration.

The cost-unit type has to be either minutes or points (e.g. story points).

The optional http-timeout field is in milliseconds. The plugin retrieves paged data from JIRA in multiple requests. This is the timeout for each one of those requests. In some situations it may be necessary to specify a value greater than the default 10,000 ms.

Example Configuration

sync:
  hour-interval: 4 # sync every 4 hours
auth:
  service:
    username: johndoe
    password: somepwd
  jira:
    base-uri: https://jira.example.com
    username: jirauser
    password: jiraapitoken
  # rest-api-path: /rest/api/latest (default)
projects:
  - key: CSE
    cost-unit:
      type: minutes
    cost-field: timeoriginalestimate
    supported-work-types:
      - Bug
      - Feature
      - Refactoring
      - Documentation
    ticket-id-pattern: (CSE-\d+)
  - key: DVP
    cost-unit:
      type: points
      format:
        singular: '%d point'
        plural: '%d points'
    cost-field: customfield_10006
    supported-work-types:
      - Bug
      - Feature
      - Refactoring
      - Documentation
    rename-work-types:
      - Bug => Unplanned Work
      - Feature => Planned Work
      - Documentation => Planned Work
    defect-and-failure-labels:
      - Bug
    work-in-progress-transition-name: Ongoing
    ticket-id-pattern: (DVP-\d+)

The previous example lists four supported-work-types (Bug, Feature, etc). These correspond to either Jira labels and/or Jira issue types. The fetched labels are used to show trends in the type of work we do.

When looking at cost trends, the most interesting distinction is typically between Planned- versus Unplanned Work. For that purpose, we include the optional configuration in rename-work-types. When available, the Jira labels will be translated to the specified label before sent to CodeScene. In this case, both Feature and Documentation labels are categorized as Planned Work, while a Bug is treated as Unplanned Work; the Refactoring label – which doesn’t have a translation – will be sent as is.

Encrypting passwords in configuration files

If you want to avoid accidentally exposing passwords by pushing the config file to a version control system or by sending it to someone else, you can leverage _optional_ encryption of passwords.

Note: if encryption is on, you have to encrypt both passwords (“service” and “jira”)!

To use encrypted passwords:

1. Turn on encryption by setting the CODESCENE_JIRA_ENCRYPTION_KEY environment variable to the encryption master key you wish to use for encrypting the passwords. The encryption key must be at least 8 characters long. 2. Manually encrypt your passwords by invoking the jira plugin with special –encrypt-password switch:

# set the master password first
$ export CODESCENE_JIRA_ENCRYPTION_KEY=xxx

Encrypt the service password:

$ java -jar target/codescene-enterprise-pm-jira.standalone.jar --encrypt-password
2019-07-22 13:01:07.659:INFO::main: Logging initialized @3976ms
Enter the password:

Encrypted password: fuJNYidHv7yo5lHO80hgD/PLxJWC7QU9iuBcmGU24UYg2uxE+r0eGIgZZBbphlGI

Encrypt the Jira password:

$ java -jar target/codescene-enterprise-pm-jira.standalone.jar --encrypt-password
2019-07-22 13:03:30.065:INFO::main: Logging initialized @4175ms
Enter the password:

Encrypted password: ukTHVi+sKNxfgpgZDuRQgZbxulx1/JbpfJmTljZTdfN4TO/GUfYbfWtnjfvXtdJZ/4ehGsOYLKoqDvJAY+6YSQ==
  1. Save the encrypted passwords in your config instead of the plaintext version:
sync:
  hour-interval: 1
auth:
  service:
    username: codescene-jira
    # encrypted version
    password: fuJNYidHv7yo5lHO80hgD/PLxJWC7QU9iuBcmGU24UYg2uxE+r0eGIgZZBbphlGI
  jira:
    base-uri: https://jira.example.com
    username: jirauser
    # encrypted version
    password: ukTHVi+sKNxfgpgZDuRQgZbxulx1/JbpfJmTljZTdfN4TO/GUfYbfWtnjfvXtdJZ/4ehGsOYLKoqDvJAY+6YSQ==
projects:
  - key: TEST
    cost-unit:
      type: minutes
    cost-field: timeoriginalestimate
    supported-work-types:
      - Bug
      - Feature
      - Refactoring
      - Documentation
    ticket-id-pattern: (TEST-\d+)

The encryption uses the AES256TextEncryptor from the well-established Jasypt library, which in turn uses the PBEWithHMACSHA512AndAES_256 algorithm. Check Jasypt’s documentation: http://www.jasypt.org/easy-usage.html

Using Story Points as Cost Estimation in Jira

When JIRA is configured to use Story Points as estimate for stories, epics, and possibly other issue types, the points will be added in a custom field JIRA creates for this purpose when Story Points is configured. The custom field will get a generated id. In order to be able to configure the PM integration service correctly, this custom field needs to be identified. The following command (against the JIRA service) using curl and jq will filter out the custom field for a project with the key CSE2 (see Atlassian Answers):

$ curl -u 'jirauser:jirapwd' \
'https://jira.example.com/rest/api/latest/issue/createmeta?expand=projects.issuetypes.fields'\
|jq '.projects[]|select(.key=="CSE2")|.issuetypes[]|select(.name=="Story")|.fields|with_entries(select(.value.name=="Story Points"))'
{
  "customfield_10006": {
    "required": false,
    "schema": {
      "type": "number",
      "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float",
      "customId": 10006
    },
    "name": "Story Points",
    "hasDefaultValue": false,
    "operations": [
      "set"
    ]
  }
}

You can verify that this is in fact the field with the Story Points. Say that you already have a story CSE2-257 with Estimate: 4 filled in, then you can find the field name and verify the points with this command:

$ curl -u 'jirauser:jirapwd' \
https://jira.example.com/rest/api/latest/issue/CSE2-257
{
  ...
  "fields": {
    ...
    "timetracking": {},
    "customfield_10006": 4,

This field name can then be placed in the configuration file:

cost-unit:
  type: points
    format:
      singular: '%d point'
      plural: '%d points'
cost-field: customfield_10006

Using Reported Time as Cost Estimation in Jira

When using Minutes instead of Points, the sync performs a search for the configured field name, usually timeoriginalestimate, having a non-empty value:

curl -u 'jirauser:jirapwd' \
'https://jira.example.com/rest/api/latest/search?jql=project=CSE2+and+timeoriginalestimate!=NULL'

Custom fields, however, cannot be searched like regular fields. Unfortunately, it seems it’s not possible to just use the complete field name, customfield_10006:

$ curl -u 'jirauser:jirapwd' \
'https://jira.example.com/rest/api/latest/search?jql=project=CSE2+and+customfield_10006!=NULL'
{
  "errorMessages": [
    "Field 'customfield_10006' does not exist or you do not have permission to view it."
  ],
  "errors": {}
}

Instead, there is a variant that uses cf[ID] (see JIRA documentation), where ID is the id of the custom field, in our case 10006. Note that the brackets must be URL-encoded, so cf[10006] turns into cf%5B10006%5D:

curl -u 'jirauser:jirapwd' \
'https://jira.example.com/rest/api/latest/search?jql=project=CSE2+and+cf%5B10006%5D!=NULL'|jq .

This means that the code for sync must detect whether a custom field is being used, extract the id, and use that in the query.