Extension talk:IssueTracker/Clif's hacks
Add topic(Moved here from IssueTracker --Nakohdo 19:02, 2 February 2011 (UTC))
Clif's Issue Tracker Hacks - Nov 12, 2009
[edit]Here are a bunch of field and functionality changes I have developed for Issue Tracker in order to make it better fit my needs. Some of these directly address previous functionality requests, and some are just things I wanted the tool to do. I hope you find them useful.
--Clif 10:29, 13 November 2009 (UTC)
Hack #1: Remove Lowercase Group Name Restriction
[edit]If you'd prefer not to have the extension dictate your group names, go to the hasPermission function in IssueTrackerAction.php and change
if ($group == strtolower($perms[$action]['group'])) {
to
if ($group == $perms[$action]['group']) {
Hack #2: Adding Fields (Priority and Severity)
[edit]I'll try to make this a good general overview of adding your own fields, and for those of you who have requested these specific fields in the past, you should be able to use this code as is.
Step 1: Add the database fields
[edit]ALTER TABLE issue_tracker
ADD COLUMN priority int(2) NOT NULL default '2',
ADD COLUMN severity int(2) NOT NULL default '3'
Step 2: Add the set and get functions for the field arrays
[edit]In IssueTracker.config.php, add these two declarations somewhere above the setPermissions() function:
protected $_issueSeverity = null;
protected $_issuePriority = null;
Next, add the following function to set up the severity array:
public function setIssueSeverity($severity = array())
{
$severity[1] = array('name' => '1 Critical', 'default' => false);
$severity[2] = array('name' => '2 Major', 'default' => true);
$severity[3] = array('name' => '3 Minor', 'default' => false);
$severity[4] = array('name' => '4 Trivial', 'default' => false);
$this->_issueSeverity = $severity;
}
If the "default" key/value pairs caught your eye, that's good, it means you're paying attention. That's part of Hack #3, so think of this as two hacks for the price of one. ;)
Now the get function:
public function getIssueSeverity()
{
if ($this->_issueSeverity === null) {
$this->setIssueSeverity();
}
return $this->_issueSeverity;
}
Here's the same thing for the priority field:
public function setIssuePriority($priority = array())
{
$priority[1] = array('name' => '1 High', 'default' => false);
$priority[2] = array('name' => '2 Medium', 'default' => true);
$priority[3] = array('name' => '3 Low', 'default' => false);
$this->_issuePriority = $priority;
}
public function getIssuePriority()
{
if ($this->_issuePriority === null) {
$this->setIssuePriority();
}
return $this->_issuePriority;
}
Step 3: Add the new fields to the Model functions
[edit]In Models\IssueTrackerModelDefault.php, find the addIssue() function and add the new fields to it:
public function addIssue($postData, $userId, $userName)
{
$data = array(
'title' => $postData['bt_title'],
'summary' => $postData['bt_summary'],
'type' => $postData['bt_type'],
'status' => $postData['bt_status'],
'assignee' => $postData['bt_assignee'],
'user_id' => $userId,
'user_name' => $userName,
'project_name' => $postData['bt_project'],
'priority_date' => date('Y-m-d H:i:s'),
'priority' => $postData['bt_priority'],
'severity' => $postData['bt_severity'],
);
return $this->_dbr->insert($this->_table, $data);
}
Of course, we need to add them to the updateIssue() function as well:
public function updateIssue($issueId, $postData)
{
$value = array(
'title' => $postData['bt_title'],
'summary' => $postData['bt_summary'],
'type' => $postData['bt_type'],
'status' => $postData['bt_status'],
'assignee' => $postData['bt_assignee'],
'priority' => $postData['bt_priority'],
'severity' => $postData['bt_severity'],
);
if ($postData['bt_status'] == 's_new') {
$value['priority_date'] = date('Y-m-d H:i:s');
}
$conds['issue_id'] = (int) $issueId;
return $this->_dbr->update($this->_table, $value, $conds);
}
Step 4: Add the get functions to the Add and View Actions
[edit]Open Actions\IssueTrackerActionAdd.php and Actions\IssueTrackerActionView.php and look for the protected function _setDefaultVars() functions. Add these two lines to both files:
$this->severityArray = $this->_config->getIssueSeverity();
$this->priorityArray = $this->_config->getIssuePriority();
Step 5: Add the get functions to the List and Edit Action
[edit]This is only slightly different from step 4. Open Actions\IssueTrackerActionList.php and find the protected function _setDefaultVars() function. Add the following two lines:
$this->issueSeverity = $this->_config->getIssueSeverity();
$this->issuePriority = $this->_config->getIssuePriority();
Open Actions\IssueTrackerActionEdit.php and find the public function editAction() function. Add the following two lines: . Add the following two lines:
'bt_priority' => $row->priority,
'bt_severity' => $row->severity,
We're almost there. :)
Step 6: Add the field names to the internationalization file
[edit]In IssueTracker.i18n.php, add the following field label definitions:
$messages['en']['severity'] = 'Severity';
$messages['en']['priority'] = 'Priority';
Step 7: Adding the new fields to the View pages
[edit]Let's look at the edit view (edit.html) first, as the table rows you'll be adding are structurally identical to the ones for status and type.
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('priority'); ?>:</strong></td>
<td><select name="bt_priority" id="bt_priority" tabindex="5" style="width: 150px">
<?php foreach ($this->priorityArray as $name => $priority): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_priority']) && $_POST['bt_priority'] == $name) ? ' selected="true"' : ''; ?>><?php echo $priority['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('severity'); ?>:</strong></td>
<td><select name="bt_severity" id="bt_severity" tabindex="6" style="width: 150px">
<?php foreach ($this->severityArray as $name => $severity): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_severity']) && $_POST['bt_severity'] == $name) ? ' selected="true"' : ''; ?>><?php echo $severity['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
You'll probably want to adjust the values for tabindex depending on where you insert your fields.
The new table row code for the add view (add.html) is slightly different because it incorporates part of hack #4 for setting default field values.
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('priority'); ?>:</strong></td>
<td><select name="bt_priority" id="bt_priority" tabindex="5" style="width: 150px">
<?php foreach ($this->priorityArray as $name => $priority): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_priority']) && $_POST['bt_priority'] == $name) ? ' selected="true"' : ((!isset($_POST['bt_priority']) && $priority['default']) ? ' selected="true"' : ''); ?>><?php echo $priority['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('severity'); ?>:</strong></td>
<td><select name="bt_severity" id="bt_severity" tabindex="6" style="width: 150px">
<?php foreach ($this->severityArray as $name => $severity): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_severity']) && $_POST['bt_severity'] == $name) ? ' selected="true"' : ((!isset($_POST['bt_severity']) && $severity['default']) ? ' selected="true"' : ''); ?>><?php echo $severity['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
Hopefully you're realizing by now that creating any new dropdown fields is pretty much a matter of search and replace in these code snippets.
Adding the new fields to the view View is a piece of cake Cake:
<tr>
<td width="150" align="left" valign="top" class="fieldBgColor">Priority</td>
<td><?php echo $this->priorityArray[$this->issue->priority]['name']; ?></td>
</tr>
<tr>
<td width="150" align="left" valign="top" class="fieldBgColor">Severity</td>
<td><?php echo $this->severityArray[$this->issue->severity]['name']; ?></td>
</tr>
I didn't add colored backgrounds to these fields because I didn't want my wiki pages to look like a bag of skittles. If you're into that sort of thing, you can add colors by mimicking the code for the status and issue type fields in add.html, view.html and IssueTracker.config.php.
Moving on to list.html, first add the column headers:
<th valign="top" align="left" width="250"><b><?php echo wfMsg('title'); ?></b></th>
<th valign="top" align="left" width="60"><b><?php echo wfMsg('priority'); ?></b></th>
<th valign="top" align="left" width="60"><b><?php echo wfMsg('severity'); ?></b></th>
<th valign="top" align="left" width="60"><b><?php echo wfMsg('status'); ?></b></th>
And finally, the value columns. I put mine before the status column:
<td><?php echo $this->issuePriority[$issue->priority]['name']; ?></td>
<td><?php echo $this->issueSeverity[$issue->severity]['name']; ?></td>
<td style="background-color: #<?php echo $this->issueStatus[$issue->status]['colour']; ?>"><?php echo $this->issueStatus[$issue->status]['name']; ?></td>
And that's it. Like I said, this should work for any dropdown field you want to add, so knock yourself out.
Hack #3: Setting default field values in IssueTracker.config.php
[edit]All of the necessary code for this hack was included in Hack #2, but here's a quick rundown of the required changes.
Step 1: Adding the "default" key/value pair
[edit]In IssueTracker.config.php, find the dropdown field(s) you want to define default values for and add a new "default" key to each field value array, with a value of "true" or "false". Obviously "true" is used to tell Issue Tracker which value is the default, so only one option should be set to "true".
public function setIssueSeverity($severity = array())
{
$severity[1] = array('name' => '1 Critical', 'default' => false);
$severity[2] = array('name' => '2 Major', 'default' => true);
$severity[3] = array('name' => '3 Minor', 'default' => false);
$severity[4] = array('name' => '4 Trivial', 'default' => false);
$this->_issueSeverity = $severity;
}
Step 2: Setting up add.html to use the default values
[edit]Again, find the field or fields you want to default to a certain value. Change the table row blocks for those fields from:
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('severity'); ?>:</strong></td>
<td><select name="bt_severity" id="bt_severity" tabindex="6" style="width: 150px">
<?php foreach ($this->severityArray as $name => $severity): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_severity']) && $_POST['bt_severity'] == $name) ? ' selected="true"' : ''; ?>><?php echo $severity['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
to:
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('severity'); ?>:</strong></td>
<td><select name="bt_severity" id="bt_severity" tabindex="6" style="width: 150px">
<?php foreach ($this->severityArray as $name => $severity): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_severity']) && $_POST['bt_severity'] == $name) ? ' selected="true"' : ((!isset($_POST['bt_severity']) && $severity['default']) ? ' selected="true"' : ''); ?>><?php echo $severity['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
That's it.
Hack #4: Setting a field default using a tag argument
[edit]So let's say you want to have a dropdown field default to a specific value depending on the project. For example, I added a field called "websystem" so I could associate bugs or tasks with different internal systems like website, admin, intranet, etc. If I have a project page set up for a project that only affects the website, I want the field to default to "website". The hack works by adding the attribute "defaultsystem" to the issues tag with a value matching the name of the websystem which will be the default.
<issues project="Web Redesign" defaultsystem="website" />
Of course, "defaultsystem" could actually be named whatever you want - "defaultcategory", "defaultassignee", etc. This is a good time to mention that attribute names are forced to lowercase, so if you name your attribute "defaultCategory" and try to reference it that way in the code, it won't work. Conversely, the value of the attribute should match your field array value exactly. So if the value is "Website" then your attribute would need to be defaultsystem="Website".
On to the code.
Step 1: Add a default for your default
[edit]In Actions\IssueTrackerActionAdd.php, you'll need to add a line to the _setDefaultVars() function that looks like this:
$this->defaultsystem = '';
Simple so far. A few lines down, in the _setHookPreferences() function, add this block:
if (array_key_exists('defaultsystem', $this->_args) && $this->_args['defaultsystem'] !== '') {
$this->defaultsystem = $this->_args['defaultsystem'];
}
Step 2: Setting up add.html to use the default
[edit]The code for this is just slightly different from the code used in Hack #3.
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('system'); ?>:</strong></td>
<td><select name="bt_websystem" id="bt_websystem" tabindex="4" style="width: 150px">
<?php foreach ($this->systemArray as $name => $websystem): ?>
<option value="<?php echo $name; ?>"<?php echo (isset($_POST['bt_websystem']) && $_POST['bt_websystem'] == $name) ? ' selected="true"' : ((!isset($_POST['bt_websystem']) && $this->defaultsystem == $websystem['name']) ? ' selected="true"' : ''); ?>><?php echo $websystem['name']; ?></option>
<?php endforeach; ?>
</select></td>
</tr>
Hack #5: Changing the default list sort order
[edit]I want my issue list sorted by priority, severity and issue id, in that order. Having the most important issues at the top make it easier to see what you should be working on first. Including issue id in the ordering criteria ensures that older issues get attention before newer ones having the same priority and severity.
This one's actually fairly simple. In Models\IssueTrackerModelDefault.php, find the getIssues() function somewhere around line 30 and change your "Order By" option from this:
'ORDER BY' => 'priority_date DESC, issue_id ASC'
to:
'ORDER BY' => 'priority ASC, severity ASC, issue_id ASC'
... or whatever else you want to order by.
Hack #6: Replacing the summary field with an HTML editor
[edit]I got tired of either dealing with one huge summary paragraph or hand-coding HTML into that little textarea pretty quick. Adding a WYSIWYG editor is actually really easy, and you'll kick yourself for not having done it before. We're going to be using the Yahoo YUI SimpleEditor because it covers all the bases and is easy to implement.
Step 1:Replace the stylesheets and JavaScript in add.html and edit.html
[edit]You're going to be making the exact same changes to both files, so I'm just going to show the changes once.
At the very top of the file (pick one), you're going to replace this code:
<link rel="stylesheet" type="text/css" href="/skins/common/editor/html/html-skin.css"/>
<script>
$(document).ready(function() {
$("#html").jTagEditor({
tagSet:"/skins/common/editor/html/html-tags.js",
tagMask:"",
insertOnShiftEnter:"\n\n",
insertOnCtrlEnter:""
});
});
</script>
with this:
<!-- Combo-handled YUI CSS files: -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.8.0r4/build/editor/assets/skins/sam/simpleeditor.css">
<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js&2.8.0r4/build/container/container_core-min.js&2.8.0r4/build/element/element-min.js&2.8.0r4/build/editor/simpleeditor-min.js"></script>
<script>
var myEditor = new YAHOO.widget.SimpleEditor('bt_summary', {
height: '300px', //150
width: '600px', //415
handleSubmit: true,
insert: true,
filterWord: true,
dompath: false //Turns on the bar at the bottom
});
myEditor.render();
</script>
Step 2: Apply the yui-skin-sam class name to a parent element
[edit]Add the YUI class to the parent <table> tag for the field table rows:
<input type="hidden" name="title" value="<?php echo $this->pageKey; ?>" />
<table width="530" border="0" class="yui-skin-sam">
<tr>
Step 3: Make a minor adjustment to the textarea tag
[edit]This change gets rid of the jTagEditor class (for the code you just ripped out) and renames the field id so Yahoo can take over your computer and turn you into a zombie geek as you spend the next 3 hours transfixed by the coolness of copying images and text from web pages and pasting them right into your new summary field. Just be sure to do all this stuff to that other file so disappointment doesn't hit you in the chest like a sledgehammer when you find yourself staring at that pitiful little textarea again.
Change this:
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('summary'); ?>:</strong></td>
<td><textarea rows="10" cols="10" tabindex="2" id="html" class="jTagEditor" accesskey="," name="bt_summary"><?php echo (isset($_POST['bt_summary'])) ? $_POST['bt_summary'] : ''; ?></textarea></td>
</tr>
to this:
<tr>
<td align="left" valign="top"><strong><?php echo wfMsg('summary'); ?>:</strong></td>
<td><textarea rows="10" cols="10" tabindex="2" id="bt_summary" accesskey="," name="bt_summary"><?php echo (isset($_POST['bt_summary'])) ? $_POST['bt_summary'] : ''; ?></textarea></td>
</tr>
... and don't say I didn't warn you.
That's all I have time for now. Enjoy, and thanks to Federico for building such a great extension.