-
Notifications
You must be signed in to change notification settings - Fork 0
Backend Customizing History Builder
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 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.
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 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.
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 are code based ways to transform the default History Item values into more complicated presentations of the event which occurred.
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.
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.
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.
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.
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>();
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.
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.
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.
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.
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.
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;
}
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.
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.
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.
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.
Once you have your templates defined to need a way for the front end to retrieve the history for your objects.
Agent has HistoryBuilder exposed as an endpoint:
/core/history?Id=1234&Type=case
This endpoint is typically called via AJAX request accepting JSON output.
The following two arguments are required.
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.
The unique identifier of the object type whose history is being built.
The following are arguments which are not required.
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.
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.
{
'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.
We hope you enjoyed this helpful Agent training document. If you see an error or omission please feel free to fork this repo to correct the change, and submit a pull request. Any questions? Contact Support.