Advanced System Reporter Customization

As a Sitecore employee, I spend a lot of time doing site reviews.  One of the best ways to get a quick snapshot of things is via the Advanced System Reporter module.  I’ve learned quite a bit about modifying the reports and creating my own using this module.

Adding Fields to Results

The first thing that I found that I didn’t realize was possible is to add in additional fields to a result set.  For example, say you wanted to view the Not Recently modified report. Your base page template has a custom field called “Region” that is used to categorize the which region the content applies to.  You want to follow-up on some of this old content and so you want to be able to see the value in the Region field.  If you edit the report, you can see that it uses the Item Viewer as the viewer for the report.  In the configuration area for the ASR module, under Viewers, if you locate the Item Viewer, you can view that to see the fields it renders.  Opening that, you can see a default list of fields that it renders, but of course, it doesn’t include the Region field. If you click the Edit button in the “Viewer” section to add new fields, the field options available do not include the Region field. As it turns out, you can add this field.. very easily!

Any of the viewer items can be edited to add or remove fields that it shows.  To do this, you can either click the drop-down next to the Column Name and choose a field that represents what you want and then click the Column Header box and give the header a name.  Clicking Add, it’ll add that and you should now see it on the results page.  You can, however, click the drop-down box and making sure that the top value is selected (this is configurable, by the way), you can simply type the name of the field, a name of the Column Header and then click the Add button.  If you need to change or correct it, or add another custom field, after you add the field, click it again and hit delete to clear the value and type a new field name.

A useful thing about this is that you can create your own custom viewer items for viewing specific fields that may be useful in multiple reports.  Then in those reports, you would simply add the new Viewer to the existing viewer on the report.  This will create a combined view of all the column headers in a single report.

Updated Filter

Here’s some simple code I adapted that is similar to the “Created Between” field, but instead allows you to filter by the Updated dates.  I put this in the Filter folder in the ASR.Reports solution.

class UpdatedBetween : BaseFilter
 {
 /// <summary>
 /// Gets from date.
 /// </summary>
 /// <value>From date.</value>
 public DateTime FromDate { get; set; }
/// <summary>
 /// Gets to date.
 /// </summary>
 /// <value>To date.</value>
 public DateTime ToDate { get; set; } /// <summary>
 /// Whether to use the first version
 /// </summary>
 /// <value>Use first version.</value>
 public bool UseFirstVersion { get; set; }
public override bool Filter(object element)
 {
 Item item = null;
 if (element is Item)
 {
 item = element as Item;
 }
 else if (element is ItemWorkflowEvent)
 {
 item = (element as ItemWorkflowEvent).Item;
 }
 if (item != null)
 {
 if (UseFirstVersion)
 {
 var versions = item.Versions.GetVersionNumbers();
 var minVersion = versions.Min(v => v.Number);
 item = item.Database.GetItem(item.ID, item.Language, new Version(minVersion)); 
 }
 DateTime dateUpdated = item.Statistics.Updated;
 if (FromDate <= dateUpdated && dateUpdated < ToDate)
 {
 return true;
 }
 }
 return false;
 }

Items with Template Inheritance

I thought I’d share a couple other things I created.  The first is that none of the parameters allow for choosing items that might use a certain template either as the type or as a base template.  This is a little resource intense, but I had a client who needed this and creating this made them very happy.. so I figure someone else will probably need this too.

The first thing you have to do is create a Template parameter.  This is basically an item selector that will only allow you to choose from templates.  Create a new item under System > Modules > ASR > Configuration > Parameters.  I called mine Template.  The type is going to be Item Selector since we’re selecting a TemplateItem.  I set my default value to the Standard template.  Next, the filter value I set to this so that it will start with the Template root item and then return back a tree for the templates.

displayresult=Name|valueresult=ID|root={3C1715FE-6A13-4FCF-845F-DE308BA9741D}|folder={3C1715FE-6A13-4FCF-845F-DE308BA9741D}|filter=@@templatename='Template' or @@templatename='Folder' or @@templatename='Template Folder'

The first thing we need is to create a custom class that does this.  Here’s the code for my class that I adapted from one of the other scanner classes:

class ContentWithInheritance : BaseScanner
 {
 public readonly static string DB_PARAMETER = "db";
 public readonly static string ROOT_PARAMETER = "root";
 public readonly static string CASCADE_PARAMETER = "search";
 public readonly static string TEMPLATE_PARAMETER = "template";
// This initializes the scan
public override ICollection Scan()
 {
 var databasename = getParameter(DB_PARAMETER);
 var db = !string.IsNullOrEmpty(databasename) ? Sitecore.Configuration.Factory.GetDatabase(databasename) ?? Sitecore.Context.ContentDatabase : Sitecore.Context.ContentDatabase;
// This grabs the item that is chosen as the root item for the items to scan. 
var rootpath = getParameter(ROOT_PARAMETER);
var rootitem = !string.IsNullOrEmpty(rootpath) ? db.GetItem(rootpath) ?? db.GetRootItem() : db.GetRootItem();
// This grabs the template that is selected by the Template parameter in our Scanner Item.
var template = getParameter(TEMPLATE_PARAMETER);
TemplateItem templateItem = !string.IsNullOrEmpty(template) ? db.GetTemplate(template) : rootitem.Template;
// This selects the scope for the scan
Item[] items;
 switch (getParameter(CASCADE_PARAMETER))
 {
 case "0": //children
 items = rootitem.Children.InnerChildren.ToArray();
 break;
 case "1": //descendants 
 items = rootitem.Axes.SelectItems("descendant-or-self::*");
 break;
 default:
 //case "-1": //item
 items = new[] { rootitem };
 break;
 }
var results = new ArrayList();

foreach (Item item in items)
 {
 if (IsTemplateDescendant(item, templateItem))
 results.Add(item);
 }
 return results;
 }
// Here's a simple check that looks at the template and then all the base templates until it finds a match. 
private static bool IsTemplateDescendant(Item item, TemplateItem template)
 {
 return ((item.TemplateID == template.ID) || IsTemplateDescendant(item.Template, template.ID));
}
private static bool IsTemplateDescendant(TemplateItem templateItem, ID itemTemplate)
 {
 if (templateItem == null || ID.IsNullOrEmpty(itemTemplate))
 {
 return false;
 }
 return ((templateItem.ID == itemTemplate) || templateItem.BaseTemplates.Any(baseTemplate => IsTemplateDescendant(baseTemplate, itemTemplate)));
 }
 }

As you might imagine, this is fairly resource intensive, so you may want to limit the items scanned rather than scanning the whole “Content” tree to start.

Next, you’ll need to create a scanner item.  I created one called Items with Template Inheritance.   This will take an Item selector parameter and a Template parameter as we saw in the code.  So the fields look like this:

Assembly:  ASR.Reports

Class:  ASR.Reports.Scanners.ContentWithInheritance

Attributes:  root={Root}|search={Search}|template={Template}

Now you can use this scanner in a report to return back only items that inherit from your chosen template. Now, for those times when you need to find all the items of a certain type, you’re all set.

I hope this was useful for someone.   Happy Sitecoreing!