| Overview You can control who can edit each field by makin small changes to the Velocity template files used to display fields in the Edit Issue screen. |
One of the points of pain with JIRA is trying to control who can edit particular fields of an issue, as discussed in JRA-1330. Various suggestions have been made there, such as using a workflow, but the page How to create a new Custom Field Type gave me the idea of simply changing the velocity template that is used to display a field to control who can edit the field's values. This approach also provides enough flexibility to make other changes such as who is permitted read the contents of a field.
Steps
- Decide which field you want to control, e.g. Fix Versions
- Find the template that is used to generate that field in the Edit Issue screen. The template is probably one of the files atlassian-jira/WEB-INF/classes/templates/jira/issue/field/*-edit.vm, e.g. versions-edit.vm in this case
If you have the source code, then you can confirm exactly which template is used by looking in jira/src/java/com/atlassian/jira/issue/fields for the field type you are interested in. - Note that some templates are used by more than one field, e.g. the versions-edit.vm is used for both the Affects Versions and Fix Versions fields.
- Find the field id of the field you want to control, e.g. for Fix Versions the field id is fixVersions. I actually found this out by simply tweaking the template to print out the $field.id, but it's really defined in atlassian-jira-enterprise-3.8-source/jira/src/java/com/atlassian/jira/issue/IIssueFieldConstants.java.
- Make the changes and restart JIRA.
Changes
This example shows the changes made to versions-edit.vm to control who can edit the Fix Versions field.
#controlHeader ($action $field.id $i18n.getText($field.nameKey) $fieldLayoutItem.required $displayParameters.get('noHeader'))
Here is where the changes start:
<!-- By default, the fields are writeable -->
#set ($readonly = "no")
#if ($field.id == "fixVersions")
<!-- This example is restricting who can change the Fix Version to
members of the fix-version-writers group -->
#if ($authcontext.user.inGroup('fix-version-writers'))
#set ($readonly = "no")
#else
#set ($readonly = "yes")
#end
#end
The following line is part of the original template
#if ($versions && !$versions.empty)
but these are the lines that change what is displayed. A "break" command would be useful in Velocity.
#if ($readonly == "yes")
<!-- Display the field value -->
#if ($currentVersions)
#foreach ($cv in $currentVersions)
#foreach ($version in $versions)
#if ($cv == $version.key)
$textutils.htmlEncode($version.value)<br>
#end
#end
#end
#else
<!-- The Fix Version has not been set -->
Unknown<br>
#end
All the other lines in this file are unchanged except for the closing #end line
#else
<select multiple name="$field.id" size="#minSelectSize ($versions 1 6)" id="$field.id">
#if (!$fieldLayoutItem.required)
<option value="$!unknownVersionId"
#if ($currentVersions && $currentVersions.contains($!unknownVersionId))selected#end
>$i18n.getText('common.words.unknown')</option>
#end
#foreach ($version in $versions)
#if ($version.key == $unreleasedVersionId || $version.key == $releasedVersionId)
#if ($optGroupOpen)
</optgroup>
#else
#set ($optGroupOpen = true)
#end
<optgroup label="$textutils.htmlEncode($version.value)">
#else
<option value="$!version.key"
#if ($currentVersions && $version && $currentVersions.contains($version.key))selected#end
>$textutils.htmlEncode($version.value)</option>
#end
#end
#if ($optGroupOpen)
</optgroup>
#end
</select>
#end <!-- readonly -->
#else
$i18n.getText('common.words.unknown')
#end
#controlFooter ($action $fieldLayoutItem.getFieldDescription() $displayParameters.get('noHeader'))
#if ($archivedVersions && !$archivedVersions.empty)
#controlHeader ($action "" $i18n.getText($archivedVersionsTitle) false false)
#foreach ($version in $archivedVersions)
<a href="$req.getContextPath()/secure/IssueNavigator.jspa?reset=true&mode=hide&sorter/order=ASC&sorter/field=priority&pid=$project.getLong('id')&$archivedVersionsSearchParam=$version.id">$textutils.htmlEncode($version.name)#if ($velocityCount < $archivedVersions.size()),#end</a>
#end
#controlFooter ($action "" false)
#end
Diff
In case that was a bit too detailed, here is the diff for JIRA 3.8.1:
[jira@toolsdev plugins]$ diff -c /data/jira/jira/atlassian-jira-enterprise-3.8-source/jira/src/etc/java/templates/jira/issue/field/versions-edit.vm ~/atlassian-jira-enterprise-3.8-standalone/atlassian-jira/WEB-INF/classes/templates/jira/issue/field/versions-edit.vm
*** /data/jira/jira/atlassian-jira-enterprise-3.8-source/jira/src/etc/java/templates/jira/issue/field/versions-edit.vm 2007-03-09 15:49:33.000000000 -0800
--- /data/jira/atlassian-jira-enterprise-3.8-standalone/atlassian-jira/WEB-INF/classes/templates/jira/issue/field/versions-edit.vm 2007-07-04 09:24:52.000000000 -0700
***************
*** 1,6 ****
--- 1,34 ----
#controlHeader ($action $field.id $i18n.getText($field.nameKey) $fieldLayoutItem.required $displayParameters.get('noHeader'))
+ <!-- By default, the fields are writeable -->
+ #set ($readonly = "no")
+ #if ($field.id == "fixVersions")
+ <!-- This example is restricting who can change the Fix Version to
+ members of the fix-version-writers group -->
+ #if ($authcontext.user.inGroup('fix-version-writers'))
+ #set ($readonly = "no")
+ #else
+ #set ($readonly = "yes")
+ #end
+ #end
+
#if ($versions && !$versions.empty)
+ #if ($readonly == "yes")
+ <!-- Display the field value -->
+ #if ($currentVersions)
+ #foreach ($cv in $currentVersions)
+ #foreach ($version in $versions)
+ #if ($cv == $version.key)
+ $textutils.htmlEncode($version.value)<br>
+ #end
+ #end
+ #end
+ #else
+ <!-- The Fix Version has not been set -->
+ Unknown<br>
+ #end
+ #else
+ <!-- All the other logic in this file is unchanged -->
<select multiple name="$field.id" size="#minSelectSize ($versions 1 6)" id="$field.id">
#if (!$fieldLayoutItem.required)
<option value="$!unknownVersionId"
***************
*** 25,30 ****
--- 53,59 ----
</optgroup>
#end
</select>
+ #end <!-- readonly -->
#else
$i18n.getText('common.words.unknown')
#end
Pros
- Simple changes to one .vm file per field to be controlled, no recompilation of source code necessary
- Uses the existing Jira group mechanism
Cons
- Need to manually apply changes to updated versions of JIRA. Happily, the changes are cleanly localised.
- Only controls fields edited using the browser, not with the SOAP API
- Need some familiarity with the Velocity template language
Troubleshooting
If you are having trouble with a hidden value being reset when the issue is edited, you can try passing it back like this:
<input type=hidden name="$customField.id" id="$customField.id" value="$value">
This may occur when a user with no write-permission for a select field edits the issue.

Comments (9)
Jul 04, 2007
Neal Applebaum says:
Thanks for sharing. I think a lot of us who are waiting on JRA-1330 want to hide...Thanks for sharing. I think a lot of us who are waiting on JRA-1330 want to hide the field(s) completely - in current and change history - e.g. create a custom field called "Estimated hours to fix" and make the issue viewable by the client, but not the value of that custom field. For us, we also want to hide assignee, so clients can't see to whom an issue is assigned.
I didn't look that closely, but do you have an example of where a field could be editable by >1 group? If not, that would be a valuable addition as an example.
Jul 05, 2007
Matt Doar says:
That's a good point. Changing a template is useful for controlling who can edit ...That's a good point. Changing a template is useful for controlling who can edit a field. It can be used to hide the field contents in the edit screen, but it can't hide the fact that such a field exists, nor prevent the contents being seen in change logs, filters etc. That's a different, and harder part, of solving this problem properly.
I didn't show how to make a field editable by more than one group, but it shouldn't be hard, e.g.
#if ($authcontext.user.inGroup('groupA') || authcontext.user.inGroup('groupB')) #set ($readonly = "no")Personally, I find the hardest thing about this approach is the Velocity language itself, which seems to lack some common statements such as "break".
~Matt
Jul 09, 2007
Thomas Krug says:
I'm having trouble locating the edit template for my custom field (uses the sele...I'm having trouble locating the edit template for my custom field (uses the select list type). How can I do that?
Thomas
Jul 09, 2007
Matt Doar says:
I suspect that the template you want is jira/src/etc/java/templates/plugins/fiel...I suspect that the template you want is jira/src/etc/java/templates/plugins/fields/edit/edit-select.vm. Don't forget that you will have to check for the correct field id that you want to control changes to - it will probably look something like customfield_10220. Let us know how you get on!
Jul 12, 2007
Thomas Krug says:
I actually added to every *.vm file a htmlEncode Statement returning the nam...I actually added to every *.vm file a htmlEncode Statement returning the name of the file and the corresponding $customfield.id like
$textutils.htmlEncode("View Rawtext")<br> $textutils.htmlEncode($customField.id)in the view-rawtext.vm. Of course the same can be done for the edit templates. There are only 17 edit and 17 view templates and you get all information you need.
My main task is to NOT display specific fields if the user is not member of a specific group. Adding something like
show's "Field blocked" instead of the field value. I'm checking Jira's Source Code for the place to not even display the whole field.
Jul 12, 2007
Matt Doar says:
That's very thorough Some templates are used for more than one customfield thou...That's very thorough
Some templates are used for more than one customfield though, so don't you have to check that the field you are not displaying is the right one somewhere?
Jul 13, 2007
Thomas Krug says:
I will have to do that. What I posted is jus version 0.1 Alpha I'm currently co...I will have to do that. What I posted is jus version 0.1 Alpha
I'm currently collection all custom field id's and will work through some sort of array/loop (whatever the velocity syntax reveals) and check the id's in all templates necessary (the rawtext is just on template in use).
I will post the new versions.
Nov 18, 2007
Joshua Goodall says:
It's worth understanding that this doesn't actually prevent a user changing a fi...It's worth understanding that this doesn't actually prevent a user changing a field value.
If they're using one of the other JIRA clients they'll have no trouble. This just hides the field when using the web interface. So it's not a secure solution.
Sep 09
Martin Burton says:
I have applied changes based on the above to versions-edit.vm as I want to restr...I have applied changes based on the above to versions-edit.vm as I want to restrict who can set the Fix Version to a released version. This is fine except that I get the problem described in the TroubleShooting section. Where do I apply the <input line?