Using Templates to control edit of an issue

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

  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.

Labels

field field Delete
permissions permissions Delete
templates templates Delete
authorization authorization Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. 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.

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

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

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

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

    +    #if ($authcontext.user.inGroup('jira-administrators'))
             #if ($value)
               $!value.toString()
            #end
    +    #else
    +      $textutils.htmlEncode("Field blocked")
    +    #end
    

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

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

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

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