Skip to content

Backend Customizing History Builder

Craig Jennings edited this page Jul 25, 2016 · 4 revisions

The collection of history data you see on a Case, Subcase, or Solution in Agent is all created by HistoryBuilder. This guide will detail the building blocks of object history in Clarify, define the front end API, and show you how to customize history output and build history for existing or custom objects.

History Background

History for objects in the Clarify system are really just Activity Entries or records in the act_entry database table. Each entry documents an activity against an object. Each entry has a unique act_code which defines the documented event.

When a Case is created an act_entry record is created with an act_code of 600 and the act_entry2case relation field is filled with the case objid value.

Activity Codes

The activity entry's activity code uniquely identifies the event. You may have or want to create custom activity entries. Your Clarify admin should be able to help here. For more information on creating custom activity entry events check out Custom Events In Clarify/Dovetail

History Items

History items model what you want to say about an event in the Clarify system. The job of History Builder is to find and transform activity entries into history items for a given Clarify object..

Here is a summary of the History Item model with comments about where the default values come from.

public class HistoryItem
{
  public string Id { get; set; } //object id
  public string Type { get; set; } //case|subcase|solution
  public DateTime When { get; set; } //act_entry.entry_time

  //GBST list "Activity Name" entry based on the rank in
  //the list of the act_entry.act_code
  public string Title { get; set; }

  public string Detail { get; set; } //act_entry.addln_info
  public string Internal { get; set; } //empty
  public HistoryItemEmployee Who { get; set; } //agent/contact details
}

The end product of the History Builder is an array of these History Items.

Need more data?

You may be thinking that the default values (in the comments above) are not very useful. What if you have an note log and you want to populate the History Item details with the contents of the note? Pulling more data related to the activity entry is needed. For this you'll need to create an ActEntry Template Policy.

ActEntry Templates

ActEntry Templates are code based ways to transform the default History Item values into more complicated presentations of the event which occurred.

Starting Simple

There are a lot of events in the Clarify system. You may not want to show all of them in History's output. In normal operation History Builder will only return history items for act entries which have an ActEntry Template Policy.

Let's take a look at an example of the simplest possible policy you can add.

ActEntry(1000);

This is not a very interesting template. It only tells History Builder that you care about activity entries with a act_code of 1000. The resulting history item for any act_code 1000 events will be based on the act_entry.

Packaging Your Templates

To get your ActEntry Templates packaged and registered with the History Builder infrastructure you need to do a few things. First we define templates in a Template Policy and then we will register that policy with History Builder.

Create a template policy expression (apologies for the long name)

First you need to package your policies into a class derived from the abstract class ActEntryTemplatePolicyExpression.

This example policy is based on one taken from our Dovetail-Bootstrap project where the History Builder code lives.

public class SimplePolicy : ActEntryTemplatePolicyExpression
{
  private IDeciderService _decider
  public SimplePolicy(IHistoryOutputParser historyOutputParser,
  IDeciderService decider) : base(historyOutputParser)
  {
    _decider = decider;
  }

  protected override void DefineTemplate(WorkflowObject workflowObject)
  {
    if(workflowObject.Type != "employee") return;

    ActEntry(1000); //like the simple policy we just talked about

    if(_decider.IsItDecided()) {
      ActEntry(1001);
      ActEntry(1002);
    }
  }
}
  • Define as many templates you like in each policy.
  • Have any logic you wish controlling which templates are created.
  • Take dependencies on services to help make decisions about what templates to create.

I hope this example shows how flexible History Builder can be when deciding why and how templates are defined. Now we need a way to register this policy with History Builder.

Template Policy Registry

It is possible to create many template policies. For advanced scenarios we need a way to control what order those policies are applied. Let's take a look at Agent's Template Policy Registry.

public class AgentActEntryTemplatePolicyRegistry
              : ActEntryTemplatePolicyRegistry
{
  public AgentActEntryTemplatePolicyRegistry()
  {
    DefaultIs<WorkflowActEntryTemplatePolicy>();
    Add<AgentSubcasePolicy>();
    Add<AgentCasePolicy>();
    // ... there are a lot of policies

    //Let's add our SimplePolicy
    Add<SimplePolicy>();
  }
}

Each of the entries in the constructor add a template policy into the list. The DefaultIs policies will be added first. The Add policies are added in the order they are given.

For most usages it is perfectly fine to add new policies to the end of the list.

Creating your own web application?

You'll need to create and register this Policy Registry with the IoC container. Here is how Agent's registry is added.

// in SupportRegistry.cs
this.ActEntryTemplatePolicies<AgentActEntryTemplatePolicyRegistry>();

Example - Add Employee History

Let's go through an example of adding suport for History to an existing Agent entity which does not currently support it. The Agent Employee doesn't have support for history. We'll be doing the following:

  • Add support to Dovetail SDK for Employee metadata.
  • Define some interesting templates for fictitious events about stock options.
  • Show how to invoke the History Builder for an employee.

Metadata

History Builder leverages a Dovetail SDK capability called, WorkflowObjectInfo, for providing details about the common relations used for common objects in the Clarify system. History Builder uses this to know how to traverse from objects like case, subcase, and solution to their act_entry records.

Unfortunately Employee is not supported out of the box and we'll need to register metadata for it. Let's take a quick look at supported objects.

Supported

The following objects have built in metadata support from Dovetail SDK for building history.

Object Id Field
SITE site_id
SCHEDULE objid
ONSITE_LOG objid
PART_TRANS objid
PROBDESC id_number
DEMAND_DTL detail_number
BUG id_number
CASE id_number
SUBCASE id_number
TASK task_id
OPPORTUNITY id
CONTRACT id
CONTACT objid
COMMIT_LOG objid
WORKAROUND objid
DIALOGUE - id_number

The id field is important to know which field history builder is expecting to uniquely identify the object whose history is being built.

Add Employee Metadata

As we mentioned previously Employee metadata is not supported out of the box by Dovetail so we'll need to register some. We'll need to tell Dovetail SDK how an employee record relates to the act_entry table. We have a simple way to do this.

Add this type to your project.

public class EmployeeWorkflowMetadata : IWorkflowObjectMetadata
{
	public WorkflowObjectInfo Register()
	{
		return new WorkflowObjectInfo(Alias)
		{
			IDFieldName = "objid",
			IsQueueableObject = false,
			ObjectName = Alias,
			ActivityRelation = "act_entry2employee"
		};
	}

	public string Alias { get { return "employee"; } }
}

Next you need to make sure implementations of IWorkflowObjectMetadata are registered with your IoC container.

public class MyProjectRegistry : Registry
{
	public CoreRegistry()
	{
		Scan(scan =>
		{
          scan.TheCallingAssembly();
          scan.WithDefaultConventions();

          //make sure this scanner config is preset
          scan.AddAllTypesOf<IWorkflowObjectMetadata>();
		});
        // Usually there are other things in here
	}
}

When your application starts up and Dovetail SDK is initialized this metadata will be registered automatically by the ClarifyApplicationFactory.

Employee Stock Option Templates

Let's create some templates for the following fictional employee events:

  • Hired 6787
  • Terminated 6788
  • Granted Stock Options 6789
  • Stock Options Exercised 6790

We will skip the template class packaging for these examples. All the code you see will be in the DefineTemplate method of a class implementing ActEntryTemplatePolicyExpression. To see the whole thing together look at this gist.

Filter templates by object

Because we are creating templates for events that only apply to an Employee we should prevent these templates from being registered for other objects.

if (workflowObject.Type != "employee")
{
  //This policy only cares about employee objects. If we didn't add
  //this guard clause these policies would be present when getting
  //history for other objects like cases, subcases, solutions.
  return;
}

Adding basic templates

These simple templates will make the Hired and Terminated events appear in History.

//History items for these two act codes will be added to the history
//with the given localized titles.
ActEntry(6787).DisplayName(EmployeeHistoryTokens.EMPLOYEE_HIRED);

//History item localizations are not required but always a good idea.
ActEntry(6788).DisplayName(EmployeeHistoryTokens.EMPLOYEE_TERMINATED);

The first template's title is localized using a StringToken.

Replacing details with related information

For the stock option grant event we wish to replace the History Item's Detail property with more details than the default value act_entry.addnl_info provided.

var stockGrantToken = StringToken.FromKeyString("STOCK_OPTION_GRANT_DETAIL", "{0} options were granted");

ActEntry(6789).DisplayName(EmployeeHistoryTokens.STOCK_OPTION_GRANT)
	.GetRelatedRecord("act_entry2stock_option")
	.WithFields("amount")
	.UpdateActivityDTOWith((row, dto) =>
	{
    //This code is run once for each act_entry with
    //act_code 6789 found for the workflow object requested.

    //By default the "dto" object is populated with
    //details from the act_entry table. There is often
    //more detail required so above we tell history
    //builder to get the related stock_option record "amount" field.

    //Here we use the amount field from the
    //stock_option record related to the act_entry.
    dto.Detail = stockGrantToken.ToFormat(row.AsInt("amount"));
	});

GetRelatedRecord tells History Builder to traverse to the stock_option table from this event's act_entry record. It is the responsibility of the act_entry record creator to relate the two records.

WithFields limits what fields are retrieved from the related stock_option record. Finally we use a localized StringToken to format the new output for this event.

UpdateActivityDTOWith is executed once for each event matching this template. The row argument is the ClarifyDataRow of the stock_option record related to this event. The dto argument is the HistoryItem which will be returned for this event. Finally, the Detail property is changed to show how many options the employee was granted.

Getting complex related data; Returning HTML

var messageFormat = @"{0} of {1} shares exercised for <a href=""transaction?id={2}"">{3}$</a>";
var stockExerciseToken = StringToken.FromKeyString("STOCK_OPTION_EXERCISE_DETAIL", messageFormat);

ActEntry(6790).DisplayName(EmployeeHistoryTokens.STOCK_OPTION_EXERCISE)
	.GetRelatedRecord("act_entry2stock_option", g =>
	{
    //this overload of GetRelatedRecord lets you provide
    //an Action on the ClarifyGeneric related to the act_entry
    //this code will get executed once while the history builder
    //is setting up all generics used to query the database.

    //Limit the fields retrieved for the stock_option
    //record to only the "amount" field.
    g.DataFields.Add("amount");

    //here we traverse yet another relation asking for only two fields
    g.TraverseWithFields("stock_option2stock_trans", "shares_trans", "amount_paid");
	})
	.UpdateActivityDTOWith((stockOptionRow, dto, template) =>
	{
    //The stockOptionRow given to this action is the
    //"related record" traversed to in GetRelatedRecord above.
    var transactionRow = stockOptionRow.RelatedRows("stock_option2stock_trans")[0];
    var totalOptionsGranted = stockOptionRow.AsInt("amount");
    var transactionId = transactionRow.DatabaseIdentifier();
    var sharesTransacted = transactionRow.AsInt("shares_trans");
    var amountPaid = Convert.ToDecimal(transactionRow["amount_paid"]);
    dto.Detail = stockExerciseToken.ToFormat(sharesTransacted, totalOptionsGranted, transactionId, amountPaid);

    //reset html encoder as we are emitting html
    //and do not want it double encoded
    template.HTMLizer = i => { };
	});

GetRelatedRecord here is a bit different than the previous example. It allows you to create an action on the traversed ClarifyGeneric. You can use this action to configure the ClarifyGeneric to do anything you like. In this example we traverse another relation to the stock_trans record and ask it to retrieve two fields.

UpdateActivityDTOWith is very similar to the previous example except we get related rows for the stock_trans record whose retrieval we setup in the action of GetRelatedRecord. Again we update the History Item Detail to have rich information about the shares exercised with a hyper-link to a url which would likely show more detail about the transaction.

Finally the template argument is used to turn off HTML encoding of the templates output. Our Detail now contains HTML so we do not want the output to get encoded.

When you turn off HTML encoding you need to be sure that all information has been sanitized or you could introduce a XSS security bug.

More Examples

If you would like more examples there are plenty in our Dovetail-Bootstrap project:

  • SeekerAttachmentPolicy shows details about attachments added to Clarify objects including a link to download the attachment.
  • WorkflowActEntryTemplatePolicy and extensions implement baseline templates for common events on case, subcase, solution.
  • Agent overrides a lot of the baseline history templates with its own. Look for types implementing ActEntryTemplatePolicyExpression in the Agent source.

API

Once you have your templates defined to need a way for the front end to retrieve the history for your objects.

Endpoint

Agent has HistoryBuilder exposed as an endpoint:

/core/history?Id=1234&Type=case

This endpoint is typically called via AJAX request accepting JSON output.

Arguments

The following two arguments are required.

Type

The type of object for which you are building history. Traditionally this is case or subcase or solution. Agent also supports history for sitepart.

You can get history for any WorkflowObject which Dovetail SDK has metadata about.

Id

The unique identifier of the object type whose history is being built.

Optional Arguments

The following are arguments which are not required.

Since

When you only want a partial list of history items you can specify an ISO DateTime argument titled Since e.g. &Since=2014-08-05 16:54:21-05:00. You will get back only history items which have occurred since that timestamp.

IsVerbose

Normally History Builder will only return history items for activity entries with act_codes which have templates defined. If you want to get back History Items for all act_entries add &IsVerbose=true to your query string.

Output

{
  'allActivitiesShown':true,
  'workflowObject':{'type':'case','id': '1234','isChild': false},
  'historyItems':[] //array of items defined above
}

It is up to the front end to render the output of each history item. Agent, on the client side, groups the history items by agent.

Clone this wiki locally