A comment on comments

I am such a sucker for new things: new gadgets, new gizmos, and new Sitecore features! So I always get excited when I notice something in Sitecore that was not there before. I immediately want to figure out what new possibilities it brings.

I had such moment the other day, when I was configuring a workflow for one of my previous posts. I discovered some new fields in a workflow command definition item which I had not seen before.

newfields

Did you know about those? I have checked the release notes but I cannot find any mention of them. After doing a bit of research I suspect they came out in v.8.

What are they for? The Comment Template field, as expected, allows you to select a Template. The purpose of this template is to define which fields should appear in the comments pop up when using the command.
Whenever an author executes a workflow command, a pop up appears providing the ability to write a comment. Authors have the choice of leaving the comment empty. Since version 6, we have been able to use the Suppress Comment check box to stop the pop up. Now we can also add extra fields to it.

I created a simple template, assigned it to the command, and tried it.

new template

And it did not do anything... hmmm. Obviously I was missing something. The functionality reminded me of the Rendering Parameter Templates (where you can define fields for the properties of the Component). When using those, you have to make sure your template inherits from a particular one, Standard Rendering Parameters. I looked for another template with the word Standard and sure enough there it was: Standard Comment Template. I set it as a base template of my one, and it worked!

wrong sized pop up

This gave me a clue as to the purpose of the second field Comment Dialog Height.

Where are my comments?

The next step, I thought, was to check my new information in the workflow history. This is available both in the Workbox and in the Workbox chunk of the Review tab. The original Comments field was showing up, as usual, but there was no trace of my extra info field.

A quick search in the source code (I currently use dotPeek) and I found that a new accompanying pipeline had also emerged: getWorkflowCommentsDisplay. By default it comes with a single step, ExtractFields:

    <getWorkflowCommentsDisplay help="Processors must accept PipelineArgs of type Sitecore.Pipelines.GetWorkflowCommentsDisplay.GetWorkflowCommentsDisplayArgs">
      <processor type="Sitecore.Pipelines.GetWorkflowCommentsDisplay.ExtractFields, Sitecore.Kernel" singleInstance="false">
        <Fields hint="list:AddField">
          <Comments>Comments</Comments>
        </Fields>
      </processor>
     </getWorkflowCommentsDisplay>

The parameter Fields allows you to define which ones should Sitecore use to populate the workflow history parts of the UI. All we need to do is add our field to the list (using an include file, of course).

<?xml version="1.0"?>  
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">  
  <sitecore>
    <pipelines>
      <getWorkflowCommentsDisplay>
        <processor type="Sitecore.Pipelines.GetWorkflowCommentsDisplay.ExtractFields, Sitecore.Kernel">
          <Fields hint="list:AddField">
            <Field>Extra info</Field>
          </Fields>
        </processor>
      </getWorkflowCommentsDisplay>
    </pipelines>
  </sitecore>
</configuration>  

And our field will show up.

field showing up

If you don't like the formatting you can add parameters to the processor to change the appearance. In addition you could use the AllFields option. These are all the available options:

<?xml version="1.0"?>  
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">  
  <sitecore>
    <pipelines>
      <getWorkflowCommentsDisplay>
        <processor type="Sitecore.Pipelines.GetWorkflowCommentsDisplay.ExtractFields, Sitecore.Kernel">
          <Fields hint="list:AddField">
            <Field>Extra info</Field>
          </Fields>
          <FieldSeperator> | </FieldSeperator> <!-- default ', ' -->
          <ValueSeperator>=</ValueSeperator> <!-- default ':' -->
          <IncludeFieldName>true</IncludeFieldName> <!-- default 'true' -->
          <AllFields>false</AllFields> <!-- default 'false' -->
        </processor>
      </getWorkflowCommentsDisplay>
    </pipelines>
  </sitecore>
</configuration>  

A few things to notice here. Beware of the word Seperator [sic]; it will catch you out like it caught me. Also IncludeFieldNames does not work, as there is a bug in the code. If you set it to false, not only the field names disappear, the actual values go away too!

Other field types

Since you are creating a template you are able to add more fields of other types. There are only two caveats, do not choose types that bring a pop up, as it does not seem to work (you are already in a pop up after all), and beware that Sitecore is storing the raw value of the field, and that is what the existing pipeline processor ExtractFields will show.

I made a more complicated example with two other fields types:

more complicated template

Notice that I am using a droplist; this is quite convenient as the raw value is the name of the selected item. I am also using a multilist; I expect this to cause an issue as the raw value is going to be a list of IDs. This is what I will get if I choose to display this field:

output with ids

I can easily make this better though, as I can add an extra pipeline step which will transform those IDs into something else. Just create a new class:

using Sitecore.Pipelines.GetWorkflowCommentsDisplay;  
using Sitecore.Text;  
namespace Workflow  
{
     public class CommentsFieldExtractor
     {
          public void Process(GetWorkflowCommentsDisplayArgs args)
          {
               var actionsRaw = args.WorkflowEvent.CommentFields["Actions"];
               if (string.IsNullOrEmpty(actionsRaw)) return;

               ListString actions = new ListString(actionsRaw);
               ListString actionNames = new ListString();
               foreach(var action in actions)
               {
               //do not be tempted to use Sitecore.Context.Database - it will be core!!
                    actionNames.Add(args.Item.Database.GetItem(action).Name);
               }
               args.Comment += ", Actions: "+actionNames.ToString();
          }
     }
}

And register it with an include file

<?xml version="1.0"?>  
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">  
  <sitecore>
    <pipelines>
      <getWorkflowCommentsDisplay>
        <processor type="Workflow.CommentsFieldExtractor,Workflow" patch:after="*[last()]"></processor>
      </getWorkflowCommentsDisplay>
    </pipelines>
  </sitecore>
</configuration>

This will show the names as expected.

full names of actions

What about the last field?

Yes, what about it? Its name did not provide me with much of a clue Appearance Evaluator Type . The word type is maybe the one that gives it away. It is expecting a fully qualified name of a class. A quick search through the code provided me with this:

namespace Sitecore.Workflows  
{
  public interface IWorkflowCommandAppearanceEvaluator
  {
    WorkflowCommandState EvaluateState(Item item, Item workflowCommandItem);

    string GetCommandName(Item item, Item workflowCommandItem);

    string GetCommandIcon(Item item, Item workflowCommandItem);

    bool HasUI(Item item, Item workflowCommandItem);

    bool SuppressComments(Item item, Item workflowCommandItem);
  }
}

A class implementing this interface is in charge of providing the information the UI needs before rendering the command on the screen. This allows changing dynamically the name and icon of the command. The method SuppressComments can stop the comments pop up from appearing, and EvaluateState is responsible of showing or hiding the command as a whole. This is all quite reminiscent of the Sitecore.Shell.Framework.Commands.Command class.
I have not been able to figure out the purpose of the HasUI method; so far it seems to just do the same as SuppressComments. For the comments pop up to show up, both SuppressComments and HasUI have to return false.

Traditionally you would allow or disallow access to a command by using the WorkflowCommandExecute access right. In addition, users can only use commands if they have write access to the item (as they are indirectly modfying one of the standard fields). Whenever you want to temporarily disable a command for a user in a particular item, you had to resort to hacks. It seems EvaluateState could help in this case.

I came up with a contrived example. Using the extra field Preferred Reviewer I had in the comment template, I will disable the command for the first 10 minutes for any users that are not the selected reviewer. The code is very easy:

namespace Workflow  
{
     public class CommandAppearanceEvaluator : BasicWorkflowCommandAppearanceEvaluator
     {
          public override WorkflowCommandState EvaluateState(Sitecore.Data.Items.Item item, Sitecore.Data.Items.Item workflowCommand)
          {
               if (item != null) //the item might be null!
               {
                    var we = Sitecore.Context.Workflow.GetWorkflow(item).GetHistory(item).OrderByDescending(i => i.Date).First();
                    var preferredReviewer = we.CommentFields["Preferred Reviewer"];
                    if (!string.IsNullOrEmpty(preferredReviewer))
                    {
                         if (Sitecore.Context.User.Name.Contains(preferredReviewer)) return WorkflowCommandState.Visible;
                         if (we.Date.AddMinutes(10) > DateTime.Now) return WorkflowCommandState.Hidden;
                    }
               }
               return base.EvaluateState(item, workflowCommand);
          }
     }
}

And then add the fully qualified name to the Appearance Evaluator Type in the definition item for the approve command. So now only the person chosen in the dropdown can use this command for the first 10 minutes after the submission. This is a simple, possibly even useless, example, but I can see where this could come useful. I even imagine linking this to my favourite Sitecore feature: the rules engine.

A few things to note:

  • The item parameter passed to the method can be null. This is when Sitecore is generating the buttons for applying the command to all and selected items in the Workbox.
  • If those apply to all and selected buttons are shown, they can still be used to apply the command even if it is not shown for the item.
  • The item will appear in the workbox, even if there are no other commands available.
  • Hiding/showing the command can impact any user, including admin, as it is unrelated to security.

I am not sure how many people will need these features, but it has been fun digging up how this new functionality works!