Documentation for JIRA 6.3 EAP developer (EAP) releases only. Not using this? See below:
(JIRA 6.2.x documentation | JIRA OnDemand documentation | earlier versions of JIRA)

Skip to end of metadata
Go to start of metadata

Overview

Icon

You can control who can edit each field by making 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

  1. Decide which field you want to control, e.g. Fix Versions
  2. 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.
  3. 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.
  4. 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.
  5. 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.

12 Comments

  1. 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.

  2. 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

  3. I'm having trouble locating the edit template for my custom field (uses the select list type). How can I do that?

     Thomas

  4. 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!

  5. I actually added to every *.vm file a htmlEncode Statement returning the name of the file and the corresponding $customfield.id like

    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.

    1. That's very thorough (smile) 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?

      1. I will have to do that. What I posted is jus version 0.1 Alpha (big grin)
        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.

  6. 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.

  7. 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?

  8. I have a rather enhanced request (wink)

    Does anybody know how the project name or key can be evaluated in velocity? I wan't to hide values the way I worked through above but only for a specific projekt... (Yes, I do reuse custom fields (smile) )

    Cheers

    Thomas

    1. if you post this question to the forum, you'll get a quick response. But I think the code you're looking for is:

      if ($issue.getProject().getString("key") == 'KEY'

  9. Anonymous

    This

    #if ($authcontext.user.inGroup('fix-version-writers'))

    No longer workings in 4.4+/5.0

    It should work using something like

    $jiraUserUtils.getGroupNamesForUser($authcontext.getLoggedInUser().getName()).contains("
    fix-version-writers
    ")