Defects: Customizing a defect plugin

TestRail comes with ready-to-use defect plugins for popular bug trackers such as Jira, FogBugz, Bugzilla and other tools. If you have customized your bug tracking tool (e.g. by adding custom fields) or if you want to add additional capabilities to a defect plugin, you can customize the built-in defect plugins to match your needs. TestRail includes the full source code of the default defect plugins and this article provides a few examples on customizing them.

If you plan to customize one of the provided defect plugins, it's recommend that you read the Building a custom defect plugin article first as it provides many important background information on the inner workings of defect plugins work. Please see below for examples on how to add additional fields to the Push Defect dialog and on how to implement user mappings.

Adding custom fields

Adding a new field to the Push Defect dialog can be useful if you are using additional (required) custom fields that the default defect plugins don't support. You might also want to add additional fields such as an Assigned To field to the dialog and this section explains how to do this. We will use the Jira defect plugin in this example, but other defect plugins work similarly. Before we customize the Jira defect plugin, we copy and rename the plugin first. We are going to copy the Jira.php plugin file from TestRail's application directory to the custom directory:

Original: <TestRail>/app/plugins/defects/Jira.php
Copy To:  <TestRail>/custom/defects/Jira_custom.php

Notice how we renamed the file to Jira_custom.php. This is important so you can still choose between the standard Jira plugin and our customized version in TestRail's administration area. Once we have copied the file, we have to change the class name. To do this, simply open the Jira_custom.php file in a text editor and adjust the class name at the beginning of the file as follows:

<?php
 
class Jira_custom_defect_plugin extends Defect_plugin
{

For this example we are going to add a new field to the Push Defect dialog that allows testers to specify the hardware and software configuration of the test machine the issue or bug occurred on. The corresponding field in Jira is a custom field of the type Text Field and is called Config. The field is valid for all issue types and is not required. The first thing we have to change is the form schema in the prepare_push method (make sure to read the reference article in case you haven't read it yet). We simply add a new string field to our dialog so that users can enter the system configuration for the bug report. We call our field config and add it right before the description field:

public function prepare_push($context)
{
	// Return a form with the following fields/properties
	return array(
		'fields' => array(
			'summary' => array(
				'type' => 'string',
				'label' => 'Summary',
				'required' => true,
				'size' => 'full'
			),
			'type' => array(
				'type' => 'dropdown',
				'label' => 'Issue Type',
				'required' => true,
				'remember' => true,
				'size' => 'compact'
			),
			'project' => array(
				'type' => 'dropdown',
				'label' => 'Project',
				'required' => true,
				'remember' => true,
				'cascading' => true,
				'size' => 'compact'
			),
			'component' => array(
				'type' => 'dropdown',
				'label' => 'Component',
				'required' => true,
				'remember' => true,
				'depends_on' => 'project',
				'size' => 'compact'
			),
			'config' => array(
				'type' => 'string',
				'label' => 'Configuration',
				'remember' => true,
				'size' => 'full'
			),
			'description' => array(
				'type' => 'text',
				'label' => 'Description'
			)
		)
	);
}

Once we have added the field to the form schema, we are going to add the config field to the prepare_field method. We do this to restore the remembered configuration the user previously entered in the dialog. This way the user doesn't have to reenter the configuration for every bug report that gets submitted. Note how we move the preference handling code to the top of the method to make use of it for the new config field:

public function prepare_field($context, $input, $field)
{
	$data = array();
 
	// Take into account the preferences of the user, but only
	// for the initial form rendering (not for dynamic loads).
	if ($context['event'] == 'prepare')
	{
		$prefs = arr::get($context, 'preferences');
	}
	else
	{
		$prefs = null;
	}
 
	// Process those fields that do not need a connection to the
	// Jira installation.		
	if ($field == 'summary' || $field == 'description' || $field == 'config')
	{
		switch ($field)
		{
			case 'summary':
				$data['default'] = $this->_get_summary_default(
					$context);
				break;
 
			case 'description':
				$data['default'] = $this->_get_description_default(
					$context);
				break;
 
			case 'config':
				$data['default'] = arr::get($prefs, 'config');
				break;
		}
 
		return $data;
	}
 
	// [ .. ]
}

Now that the script restores previously entered configuration settings, the last remaining step is to add the new custom field to the API request we send to Jira (in order to create the issue). We change our push method for this as follows:

public function push($context, $input)
{
	$api = $this->_get_api();
 
	$data = array();
	$data['summary'] = $input['summary'];
	$data['type'] = $input['type'];
	$data['project'] = $input['project'];
	$data['component'] = $input['component'];
	$data['description'] = $input['description'];
	$data['customFieldValues'] = array(
		array(
			'customfieldId' => 'customfield_10000',
			'values' => array($input['config'])
		)
	);
 
	return $api->add_issue($data);
}

You would need to adjust the customfield_10000 name and use the actual ID of the custom field(s) you want to submit. Now that we've added the new field to our custom defect plugin, our updated Push Defect dialog can be used to submit the configuration details along with the issue (make sure to select the new Jira_custom plugin under Administration > Site Settings in order to see the new field).

You can download the full sample file here: jira_custom.zip

Adding built-in fields

Adding additional built-in fields of your bug tracker to TestRail's Push Defect is not much different than adding your bug tracker's custom fields as explained above. However, because this is such a common task and some bug trackers' APIs require special conventions for specific fields, this section explains how to add an additional built-in bug tracker field to a defect plugin. Specifically, this section explains how to add the Affects Version field of Jira, but other bug trackers have similar fields and conventions. The Affects Version field is implemented as a dropdown field to select a version the new bug should be reported for. We first copy our standard Jira defect plugin to our custom directory and rename it:

Original: <TestRail>/app/plugins/defects/Jira.php
Copy To:  <TestRail>/custom/defects/Jira_versions.php

We also need to adjust the class name at the beginning of the file again:

<?php
 
class Jira_versions_defect_plugin extends Defect_plugin
{

The first real code change involves adding the new field to the form schema. We will call this field affects_version:

public function prepare_push($context)
{
	// Return a form with the following fields/properties
	return array(
		'fields' => array(
			'summary' => array(
				'type' => 'string',
				'label' => 'Summary',
				'required' => true,
				'size' => 'full'
			),
			'type' => array(
				'type' => 'dropdown',
				'label' => 'Issue Type',
				'required' => true,
				'remember' => true,
				'size' => 'compact'
			),
			'project' => array(
				'type' => 'dropdown',
				'label' => 'Project',
				'required' => true,
				'remember' => true,
				'cascading' => true,
				'size' => 'compact'
			),
			'component' => array(
				'type' => 'dropdown',
				'label' => 'Component',
				'required' => true,
				'remember' => true,
				'depends_on' => 'project',
				'size' => 'compact'
			),
			'affects_version' => array(
				'type' => 'dropdown',
				'label' => 'Affects Version',
				'required' => false,
				'remember' => true,
				'depends_on' => 'project',
				'size' => 'compact'
			),
			'description' => array(
				'type' => 'text',
				'label' => 'Description'
			)
		)
	);
}

In order to actually query the available version for a project from Jira, we are going to add a new get_versions method to the Jira_api class:

/**
 * Get Versions
 *
 * Returns a list of versions for a Jira project. The versions
 * are returned as array of objects, each with its ID and name.
 */	
public function get_versions($project_id)
{
	$data = array($project_id);
	$response = $this->_send_command('getVersions', $data);
 
	if (!$response)
	{
		return array();
	}
 
	$result = array();
	foreach ($response as $version)
	{
		$c = obj::create();
		$c->id = (string) $version->id;
		$c->name = (string) $version->name;
		$result[] = $c;
	}
 
	return $result;
}

We also need to fill our dropdown field with the available versions from Jira, so we update the prepare_field method and add the following code to the second switch statement:

case 'affects_version':
	if (isset($input['project']))
	{
		$data['default'] = arr::get($prefs, 'affects_version');
		$data['options'] = $this->_to_id_name_lookup(
			$api->get_versions($input['project'])
		);
	}
	break;

Last but not least we also need to actually push the version field to Jira when a user submits the bug report. As Jira supports multiple versions for an issue, we need to submit this field as an array. To do this, simply add the following code to add_issue method:

if (isset($options['affects_version']))
{
	$version = array(
		'id' => $options['affects_version'],
		'archived' => false,
		'released' => false
	);
	$options['affectsVersions'] = array(
		$version
	);
}

When you select the new Jira_version defect plugin under Administration > Site Settings > Integration tab, the Push Defect dialog looks like this:

You can download the full sample file here: jira_versions.zip

Implementing user mappings

Please note: There's now an easier way to map users between TestRail and your bug tracking tool without making any code changes. You can learn more about this in the user variables article.

The defect plugins that ship with TestRail use a single user account to submit bug reports to the respective third-party system. This works well in many situations and it's recommended to add a special testrail user or similar to the bug tracker for this. However, if you want to use the actual bug tracker's user account of the user who pushed a bug report, we can implement this by adding a user mapping to the defect plugin. To demonstrate this, we are going to copy and rename the Jira.php plugin again:

Original: <TestRail>/app/plugins/defects/Jira.php
Copy To:  <TestRail>/custom/defects/Jira_users.php

And we are also going to update the defect plugin's class name:

<?php
 
class Jira_users_defect_plugin extends Defect_plugin
{

To add user mapping capabilities to the script, we are going to add a new [users] section to the configuration that allows administrators to configure a mapping between the TestRail user (identified by the user's email address) and the login credentials of the defect tracking tool. For example, the configuration in the administration area would look like this:

; Please configure your Jira connection below
[connection]
address=http://jira/
user=testrail
password=secret

[users]
bob@example.com=bob:secret
jim@example.com=jim:secret
jane@example.com=jane:secret

When Bob is submitting a bug report, the updated defect script will use Bob's Jira credentials to submit the bug. If a user whose credentials aren't configured yet tries to submit a bug report, the script will fallback to the globally configured login (specified in the [connection] section). We are going to update the get_meta method in order to show our new configuration options:

private static $_meta = array(
	'author' => 'Gurock Software',
	'version' => '1.0',
	'description' => 'Jira defect plugin for TestRail',
	'can_push' => true,
	'can_lookup' => true,
	'default_config' => 
		'; Please configure your Jira connection below
[connection]
address=http://<your-server>/
user=testrail
password=secret
 
[users]
user@example.com=user:secret'
);

And we also update the configure method to assign the [users] section to an internal field:

public function configure($config)
{
	$ini = ini::parse($config);
	$this->_address = str::slash($ini['connection']['address']);
	$this->_user = $ini['connection']['user'];
	$this->_password = $ini['connection']['password'];
 
	if (isset($ini['users']))
	{
		$this->_users = $ini['users'];
	}
	else
	{
		$this->_users = array();
	}
}

The next thing we have to change is the actual Jira API authentication. To do this we have to add our user mapping code to the _get_api method like this:

private function _get_api($context = null)
{
	if ($this->_api)
	{
		return $this->_api;
	}
 
	$user = $this->_user;
	$password = $this->_password;
 
	// Find the appropriate Jira user for the bug reporter
	// (by mapping the current TestRail user to her Jira
	// account).
	if ($context && $this->_users)
	{
		$credentials = arr::get(
			$this->_users,
			str::to_lower($context['user']->email)
		);
 
		if ($credentials)
		{
			if (preg_match('/([^:]+):(.+)/', $credentials, $matches))
			{
				$user = $matches[1];
				$password = $matches[2];
			}
		}
	}
 
	$this->_api = new Jira_api($this->_address);
	$this->_api->login($user, $password);
	return $this->_api;
}

The method tries to find the corresponding user credentials of the current user in the configuration. If it cannot find the credentials, it uses the globally configured credentials as fallback. Note how we have added the $context argument to the _get_api method. We need this argument in order to get the email address of the current TestRail user. Now we just have to pass the $context variable from the prepare_field and push methods like this (please note that you should not add the $context argument in the lookup method, as it doesn't have any context information):

$api = $this->_get_api($context);

That's it. Now that we have added the user mapping to the _get_api method and also passed the context information to this method, all that is left to do is to select our new defect plugin under Administration > Site Settings and configure the user accounts.

User mapping alternative

Please note: There's now an easier way to map users between TestRail and your bug tracking tool without making any code changes. You can learn more about this in the user variables article.

Some issue and defect trackers allow you to set the reporter user via the API. This way you don't necessarily have to implement a full user mapping via logins, but you can simply specify the username of the user that reported the issue. For example, let's say the email addresses of your organization and the corresponding JIRA usernames look like this:

bob@example.com -> bob
jan@example.com -> jan
sue@example.com -> sue

You could then simply build the JIRA username from the email address of the current user and specify the reporter field in the push method like this:

public function push($context, $input)
{
	if (isset($context['user']->email))
	{
		$email = $context['user']->email;
		$pos = str::pos($email, '@');
		if  ($pos !== false)
		{
			$input['reporter'] = str::sub($email, 0, $pos);
		}
	}
 
	$api = $this->_get_api();
	return $api->add_issue($input);
}

This way the user who reported the issue is correctly sent to JIRA without having to store the username and password of the JIRA user in TestRail. Other issue and defect tracking tools might also support this, depending on the provided API capabilities.