Skip to main content
Version: 8.0

Add a custom flow to a new campaign element

Level: advanced

Use Campaign designer to set up your marketing campaigns. You can create a visual campaign diagram that would consist of interconnected pre-configured elements. You can also add custom campaign elements.

Algorithm of adding a custom flow (an arrow)

  1. Create a new schema for the Flow element.
  2. Create the edit page for the Flow element properties.
  3. Create the server part for the Flow element.
  4. Create the executed element for the flow transition.
  5. Create the CampaignConnectorManager replacing module for adding the flow operation logic.
  6. Connect the CampaignConnectorManager replacing module.
Example

Create a custom flow (an arrow) from the new campaign element for sending SMS-messages to a user and add a possibility to select the bulk sms response condition. On the setup page, a user can select an option to either ignore the responses or take them into consideration based on the list of possible bulk sms response types. If no response type is selected, the flow is enabled for any response.

Important

In this case, the Usr prefix is not used in schema names. You can change the prefix used by default in the Prefix for object name system setting (the SchemaNamePrefix code).

Important

Perform steps 1-6 from the "Add a custom campaign element" article before you implement the case.

Example implementation algorithm

1. Creating the TestSmsTarget and TestSmsResponseType objects

Create schemas for the TestSmsTarget and TestSmsResponseType objects in the development package.

Learn more about creating object schemas in the Object article.

Add a column with the following properties to the TestSmsResponseType schema:

  • Title – "Name"
  • Name – "Name"
  • Data type – "Text (50 characters)"

Add columns with the following properties to the TestSmsTarget schema:

Primary column properties of the TestSmsTarget object schema

Title

Name

Data type

Phone number

PhoneNumber

"Text (50 characters)"

SMS text

SmsText

"Text (50 characters)"

Contact

Contact

"Lookup" – "Contact"

Test SMS response

TestSmsResponse

"Lookup" – "TestSmsResponseType"

Learn more about adding object schema columns in the Object article.

Columns and properties of the TestSmsTarget object schema
Columns and properties of the TestSmsTarget object schema
Columns and properties of the TestSmsResponseType object schema
Columns and properties of the TestSmsResponseType object schema

Save and publish the objects.

Create a lookup for the TestSmsResponseType object and populate it with the "Sms delivered", "Canceled", "Error while receiving", etc. values. The response identifiers (Ids) will be used in the edit page code of the condition flow properties from the bulk SMS.

2. Creating a new schema for the flow element

To display the element in the Campaign designer user interface, create a new module schema in the development package. The procedure for creating a module schema is covered in the Client module article. Set the following properties for the created schema:

  • Title – "ProcessTestSmsConditionalTransitionSchema."
  • Name – "ProcessTestSmsConditionalTransitionSchema."
Important

The schema name for the arrow must contain the "Process" prefix

Add the following source code to the Source code section of the schema:

ProcessTestSmsConditionalTransitionSchema
define("ProcessTestSmsConditionalTransitionSchema", ["CampaignEnums", "ProcessTestSmsConditionalTransitionSchemaResources",  "ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) {
Ext.define("Terrasoft.manager.ProcessTestSmsConditionalTransitionSchema", {
extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema",
alternateClassName: "Terrasoft.ProcessTestSmsConditionalTransitionSchema",
managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444",
mixins: {
parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement"
},
// The full type name of the connected arrow element.
typeName: "Terrasoft.Configuration.TestSmsConditionalTransitionElement, Terrasoft.Configuration",
// The name of the arrow element for connecting to campaign elements.
connectionUserHandleName: "TestSmsConditionalTransition",
// The name of the arrow property setup page.
editPageSchemaName: "TestSmsConditionalTransitionPropertiesPage",
elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION,
// Bulk sms response collection.
testSmsResponseId: null,
// The checkbox that takes into consideration the response condition for contact transition.
isResponseBasedStart: false,
getSerializableProperties: function() {
var baseSerializableProperties = this.callParent(arguments);
// Properties for serialization and transfer to the server part when saving.
Ext.Array.push(baseSerializableProperties, ["testSmsResponseId", "isResponseBasedStart"]);
return baseSerializableProperties;
}
});
return Terrasoft.ProcessTestSmsConditionalTransitionSchema;
});

Save the created schema.

3. Creating the edit page of the flow element properties

To display and modify campaign element properties, create its edit page in the development package. Create the schema replacing CampaignConditionalSequenceFlowPropertiesPage (the CampaignDesigner package). The procedure for creating a replacing schema is covered in the Client module article.

Set the following properties for the created schema:

  • Title – "TestSmsConditionalTransitionPropertiesPage."
  • Name – "TestSmsConditionalTransitionPropertiesPage."
  • Parent object – "CampaignConditionalSequenceFlowPropertiesPage."

Add localizable strings, whose properties are listed in table to the created schema.

Primary properties of the localizable strings

Name

Value

ReactionModeCaption

What is the result of the 0 step?

ReactionModeDefault

Transfer participants regardless of their response

ReactionModeWithCondition

Set up responses for transferring participants

IsTestSmsDelivered

Test SMS delivered

IsErrorWhileReceiving

Error while receiving

Add the following source code to the Source code section of the schema:

TestSmsConditionalTransitionPropertiesPage
define("TestSmsConditionalTransitionPropertiesPage", ["BusinessRuleModule"], function(BusinessRuleModule) {
return {
messages: {},
attributes: {
"ReactionModeEnum": {
dataValueType: this.Terrasoft.DataValueType.CUSTOM_OBJECT,
type: this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN,
value: {
Default: {
value: "0",
captionName: "Resources.Strings.ReactionModeDefault"
},
WithCondition: {
value: "1",
captionName: "Resources.Strings.ReactionModeWithCondition"
}
}
},
"ReactionMode": {
"dataValueType": this.Terrasoft.DataValueType.LOOKUP,
"type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN,
"isRequired": true
},
"IsTestSmsDelivered": {
"dataValueType": this.Terrasoft.DataValueType.BOOLEAN,
"type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN
},
"IsErrorWhileReceiving": {
"dataValueType": this.Terrasoft.DataValueType.BOOLEAN,
"type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN
}
},

rules:
{
"ReactionConditionDecision": {
"BindReactionConditionDecisionRequiredToReactionMode": {
"ruleType": BusinessRuleModule.enums.RuleType.BINDPARAMETER,
"property": BusinessRuleModule.enums.Property.REQUIRED,
"conditions": [{
"leftExpression": {
"type": BusinessRuleModule.enums.ValueType.ATTRIBUTE,
"attribute": "ReactionMode"
},
"comparisonType": this.Terrasoft.ComparisonType.EQUAL,
"rightExpression": {
"type": BusinessRuleModule.enums.ValueType.CONSTANT,
"value": "1"
}
}]
}
}
},
methods: {
// Ensures sms-response correspondence (based on the TestSmsResponseType lookup).
// We assume that Creatio already contains the TestSmsResponseType lookup
// with the TestSmsDelivered and ErrorWhileReceiving records.
getResponseConfig: function() {
return {
"IsTestSmsDelivered": "F2FC75B3-58C3-49A6-B2F2-353262068145",
"IsErrorWhileReceiving": "37B9F9D5-E897-4B7B-A65E-3B3799A18D72"
};
},

subscribeEvents: function() {
this.callParent(arguments);
// Connecting the handler to the event of changing the ReactionMode attribute value
this.on("change:ReactionMode", this.onReactionModeLookupChanged, this);
},

// Event handler-method of changing the ReactionMode attribute.
onReactionModeLookupChanged: function() {
var reactionModeEnum = this.get("ReactionModeEnum");
var reactionMode = this.get("ReactionMode");
var decisionModeEnabled = (reactionMode && reactionMode.value === reactionModeEnum.WithCondition.value);
if (!decisionModeEnabled) {
this.set("ReactionConditionDecision", null);
}
},

// Initiates the viewModel properties to display the page when opening.
initParameters: function(element) {
this.callParent(arguments);
var isResponseBasedStart = element.isResponseBasedStart;
this.initReactionMode(isResponseBasedStart);
this.initTestSmsResponses(element.testSmsResponseId);
},

// Auxiliary method cutting the line to the specified length and adding allipsis at the end.
cutString: function(strValue, strLength) {
var ellipsis = Ext.String.ellipsis(strValue.substring(strLength), 0);
return strValue.substring(0, strLength) + ellipsis;
},

// Sets the status value to "Sms delivered".
initIsTestSmsDelivered: function(value) {
if (value === undefined) {
value = this.get("IsTestSmsDelivered");
}
this.set("IsTestSmsDelivered", value);
},

// Sets the status value to "Error while receiving".
initIsErrorWhileReceiving: function(value) {
if (value === undefined) {
var isErrorWhileReceiving = this.get("IsErrorWhileReceiving");
value = isErrorWhileReceiving;
}
this.set("IsErrorWhileReceiving", value);
},

// Initiates the selected responses when opening the page.
initTestSmsResponses: function(responseIdsJson) {
if (!responseIdsJson) {
return;
}
var responseIds = JSON.parse(responseIdsJson);
var config = this.getResponseConfig();
Terrasoft.each(config, function(propValue, propName) {
if (responseIds.indexOf(propValue) > -1) {
this.set(propName, true);
}
}, this);
},

initReactionMode: function(value) {
var isDefault = !value;
this.setLookupValue(isDefault, "ReactionMode", "WithCondition", this);
},

// Auxiliary method extracting the identifier array from the incoming JSON parameter.
getIds: function(idsJson) {
if (idsJson) {
try {
var ids = JSON.parse(idsJson);
if (this.Ext.isArray(ids)) {
return ids;
}
} catch (error) {
return [];
}
}
return [];
},

onPrepareReactionModeList: function(filter, list) {
this.prepareList("ReactionModeEnum", list, this);
},

// Saves the response values and the setting of adding the response condition.
saveValues: function() {
this.callParent(arguments);
var element = this.get("ProcessElement");
var isResponseBasedStart = this.getIsReactionModeWithConditions();
element.isResponseBasedStart = isResponseBasedStart;
element.testSmsResponseId = this.getTestSmsResponseId(isResponseBasedStart);
},

// Receives the serialized Ids of the selected responses.
getTestSmsResponseId: function(isResponseActive) {
var responseIds = [];
if (isResponseActive) {
var config = this.getResponseConfig();
Terrasoft.each(config, function(propValue, propName) {
var attrValue = this.get(propName);
if (attrValue && propValue) {
responseIds.push(propValue);
}
}, this);
}
return JSON.stringify(responseIds);
},

getLookupValue: function(parameterName) {
var value = this.get(parameterName);
return value ? value.value : null;
},

getContextHelpCode: function() {
return "CampaignConditionalSequenceFlow";
},

getIsReactionModeWithConditions: function() {
return this.isLookupValueEqual("ReactionMode", "1", this);
},

getSourceElement: function() {
var flowElement = this.get("ProcessElement");
if (flowElement) {
return flowElement.findSourceElement();
}
return null;
},
// Adds the name of the element that the arrow is generated from to the text.
getQuestionCaption: function() {
var caption = this.get("Resources.Strings.ReactionModeCaption");
caption = this.Ext.String.format(caption, this.getSourceElement().getCaption());
return caption;
}
},
diff: /**SCHEMA_DIFF*/[
// Container.
{
"operation": "insert",
"name": "ReactionContainer",
"propertyName": "items",
"parentName": "ContentContainer",
"className": "Terrasoft.GridLayoutEdit",
"values":
{
"layout":
{
"column": 0,
"row": 2,
"colSpan": 24
},
"itemType": this.Terrasoft.ViewItemType.GRID_LAYOUT,
"items": []
}
},
// Title.
{
"operation": "insert",
"name": "ReactionModeLabel",
"parentName": "ReactionContainer",
"propertyName": "items",
"values":
{
"layout":
{
"column": 0,
"row": 0,
"colSpan": 24
},
"itemType": this.Terrasoft.ViewItemType.LABEL,
"caption":
{
"bindTo": "getQuestionCaption"
},
"classes":
{
"labelClass": ["t-title-label-proc"]
}
}
},
// List.
{
"operation": "insert",
"name": "ReactionMode",
"parentName": "ReactionContainer",
"propertyName": "items",
"values":
{
"contentType": this.Terrasoft.ContentType.ENUM,
"controlConfig":
{
"prepareList":
{
"bindTo": "onPrepareReactionModeList"
}
},
"isRequired": true,
"layout":
{
"column": 0,
"row": 1,
"colSpan": 24
},
"labelConfig":
{
"visible": false
},
"wrapClass": ["no-caption-control"]
}
},
// List element.
{
"operation": "insert",
"parentName": "ReactionContainer",
"propertyName": "items",
"name": "IsTestSmsDelivered",
"values":
{
"wrapClass": ["t-checkbox-control"],
"visible":
{
"bindTo": "ReactionMode",
"bindConfig":
{
converter: "getIsReactionModeWithConditions"
}
},
"caption":
{
"bindTo": "Resources.Strings.IsTestSmsDelivered"
},
"layout":
{
"column": 0,
"row": 2,
"colSpan": 22
}
}
},
// List element.
{
"operation": "insert",
"parentName": "ReactionContainer",
"propertyName": "items",
"name": "IsErrorWhileReceiving",
"values":
{
"wrapClass": ["t-checkbox-control"],
"visible":
{
"bindTo": "ReactionMode",
"bindConfig":
{
converter: "getIsReactionModeWithConditions"
}
},
"caption":
{
"bindTo": "Resources.Strings.IsErrorWhileReceiving"
},
"layout":
{
"column": 0,
"row": 3,
"colSpan": 22
}
}
}
]/**SCHEMA_DIFF*/
};
});

Save the created schema.

4. Creating the server part of the flow element from the Bulk SMS element

To implement saving the base and user campaign element properties, create a class interacting with the server part of the application. The class should inherit CampaignSchemaElement and override the ApplyMetaDataValue() and WriteMetaData() methods.

Create the source code schema with the following properties:

  • Title – "TestSmsConditionalTransitionElement."
  • Name – "TestSmsConditionalTransitionElement."

Creating the source code schema is covered in the Source code (C#) article.

Add the following source code to the Source code section of the schema:

TestSmsConditionalTransitionElement
namespace Terrasoft.Configuration
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Terrasoft.Common;
using Terrasoft.Core;
using Terrasoft.Core.Campaign;
using Terrasoft.Core.DB;
using Terrasoft.Core.Process;

[DesignModeProperty(Name = "TestSmsResponseId",
UsageType = DesignModeUsageType.NotVisible, MetaPropertyName = TestSmsResponseIdPropertyName)]
[DesignModeProperty(Name = "IsResponseBasedStart",
UsageType = DesignModeUsageType.Advanced, MetaPropertyName = IsResponseBasedStartPropertyName)]
public class TestSmsConditionalTransitionElement : ConditionalSequenceFlowElement
{

private const string TestSmsResponseIdPropertyName = "TestSmsResponseId";
private const string IsResponseBasedStartPropertyName = "IsResponseBasedStart";

public TestSmsConditionalTransitionElement() {}

public TestSmsConditionalTransitionElement(TestSmsConditionalTransitionElement source)
: this(source, null, null) {}

public TestSmsConditionalTransitionElement(TestSmsConditionalTransitionElement source,
Dictionary<Guid, Guid> dictToRebind, Core.Campaign.CampaignSchema parentSchema)
: base(source, dictToRebind, parentSchema) {
IsResponseBasedStart = source.IsResponseBasedStart;
_testSmsResponseIdJson = JsonConvert.SerializeObject(source.TestSmsResponseId);
}

private string _testSmsResponseIdJson;

private IEnumerable<Guid> Responses {
get {
return TestSmsResponseId;
}
}

[MetaTypeProperty("{DC597899-B831-458A-A58E-FB43B1E266AC}")]
public IEnumerable<Guid> TestSmsResponseId {
get {
return !string.IsNullOrWhiteSpace(_testSmsResponseIdJson)
? JsonConvert.DeserializeObject<IEnumerable<Guid>>(_testSmsResponseIdJson)
: Enumerable.Empty<Guid>();
}
}

[MetaTypeProperty("{3FFA4EA0-62CC-49A8-91FF-4096AEC561F6}",
IsExtraProperty = true, IsUserProperty = true)]
public virtual bool IsResponseBasedStart {
get;
set;
}

protected override void ApplyMetaDataValue(DataReader reader) {
base.ApplyMetaDataValue(reader);
switch (reader.CurrentName) {
case TestSmsResponseIdPropertyName:
_testSmsResponseIdJson = reader.GetValue<string>();
break;
case IsResponseBasedStartPropertyName:
IsResponseBasedStart = reader.GetBoolValue();
break;
default:
break;
}
}

public override void WriteMetaData(DataWriter writer) {
base.WriteMetaData(writer);
writer.WriteValue(IsResponseBasedStartPropertyName, IsResponseBasedStart, false);
writer.WriteValue(TestSmsResponseIdPropertyName, _testSmsResponseIdJson, null);
}

public override object Clone() {
return new TestSmsConditionalTransitionElement(this);
}

public override object Copy(Dictionary<Guid, Guid> dictToRebind, Core.Campaign.CampaignSchema parentSchema) {
return new TestSmsConditionalTransitionElement(this, dictToRebind, parentSchema);
}

// Overrides the factory method for creating the executed element
// Returns the element with the TestSmsConditionalTransitionFlowElement type
public override ProcessFlowElement CreateProcessFlowElement(UserConnection userConnection) {
var sourceElement = SourceRef as TestSmsElement;
var executableElement = new TestSmsConditionalTransitionFlowElement {
UserConnection = userConnection,
TestSmsResponses = TestSmsResponseId,
PhoneNumber = sourceElement.PhoneNumber,
SmsText = sourceElement.SmsText
};
InitializeCampaignProcessFlowElement(executableElement);
InitializeCampaignTransitionFlowElement(executableElement);
InitializeConditionalTransitionFlowElement(executableElement);
return executableElement;
}
}
}

Save and publish the created schema.

5. Creating the executed element for the flow from the Bulk SMS element

To add a functionality that would take into consideration the response type as per the sent bulk sms, create the executed element. It is a class, the inheritor of the ConditionalTransitionFlowElement class.

To create the executed element, add the source code schema with the following properties in the development package:

  • Title – "TestSmsConditionalTransitionElement."
  • Name – "TestSmsConditionalTransitionElement."

Add the following source code to the Source code section of the schema:

TestSmsConditionalTransitionFlowElement
namespace Terrasoft.Configuration
{
using System;
using System.Collections.Generic;
using System.Linq;
using Terrasoft.Common;
using Terrasoft.Core.DB;

public class TestSmsConditionalTransitionFlowElement : ConditionalTransitionFlowElement
{

public string SmsText { get; set; }

public string PhoneNumber { get; set; }

public IEnumerable<Guid> TestSmsResponses { get; set; }

private void ExtendWithResponses() {
TransitionQuery.CheckArgumentNull("TransitionQuery");
if (TestSmsResponses.Any()) {
Query responseSelect = GetSelectByParticipantResponses();
TransitionQuery.And("ContactId").In(responseSelect);
}
}

private Query GetSelectByParticipantResponses() {
var responseSelect = new Select(UserConnection)
.Column("ContactId")
.From("TestSmsTarget")
.Where("SmsText").IsEqual(Column.Parameter(SmsText))
.And("PhoneNumber").IsEqual(Column.Parameter(PhoneNumber))
.And("TestSmsResponseId")
.In(Column.Parameters(TestSmsResponses)) as Select;
responseSelect.SpecifyNoLockHints(true);
return responseSelect;
}

protected override void CreateQuery() {
base.CreateQuery();
ExtendWithResponses();
}
}
}

Save and publish the created schema.

6. Creating the CampaignConnectorManager replacing module for adding the flow operation logic

To add specific logic of the flow operation upon changing the source (i.e. the element that the outgoing arrow is generated from), create a new module schema replacing the CampaignConnectorManager module in the development package. The procedure for creating a module schema is covered in the Client module article. Set the following properties for the created schema:

  • Title – "TestSmsCampaignConnectorManager."
  • Name – "TestSmsCampaignConnectorManager."

Add the following source code to the Source code section of the schema:

TestSmsCampaignConnectorManager
define("TestSmsCampaignConnectorManager", [], function() {

Ext.define("Terrasoft.TestSmsCampaignConnectorManager", {
// Specify replacing of the CampaignConnectorManager module
override: "Terrasoft.CampaignConnectorManager",

// Add mapping of the name of arrow source campaign element – arrow type (full name)
initMappingCollection: function() {
this.callParent(arguments);
this.connectorTypesMappingCollection.addIfNotExists("TestSmsElementSchema",
"Terrasoft.ProcessTestSmsConditionalTransitionSchema");
},

// Virtual method for reload
// Arrow processing logic before its sudstitute by an arrow with a new type.
additionalBeforeChange: function(prevTransition, sourceItem, targetItem) {
// additional logic here
},

// Virtual method for reload
// Populating specific fields of the created arrow based on the previous arrow.
fillAdditionalProperties: function(prevElement, newElement) {
if (newElement.getTypeInfo().typeName === "ProcessTestSmsConditionalTransitionSchema") {
// Copy the configured responses if the previous arrow is of the same type
newElement.testSmsResponseId = prevElement.testSmsResponseId ? prevElement.testSmsResponseId : null;
// Copy the configuration of response setup
newElement.isResponseBasedStart = prevElement.isResponseBasedStart
? prevElement.isResponseBasedStart
: false;
}
}
});
});

Save the created schema.

7. Connecting the CampaignConnectorManager replacing module

To connect the module created on the previous step, create a replacing client module and specify BootstrapModulesV2 from the NUI package as the parent object. The procedure of creating a replacing client module is covered in the Client module article.

Add the following source code to the Source code section of the schema:

BootstrapModulesV2
// Set the previously created TestSmsCampaignConnectorManager module as a dependency
define("BootstrapModulesV2", ["TestSmsCampaignConnectorManager"], function() {});

Save the created schema.

note

During an actual implementation of this case, we recommend creating a separate Bulk SMS object schema. The TestSmsElement and TestSmsConditionalTransitionElement objects will contain the Id of this object and not the SmsText, PhoneNumber ... fields. The TestSmsCampaignProcessElement executed element in the Execute() method must contain the logic of adding contacts to the bulk sms audience. A separate mechanism (or several mechanisms) must perform sending of the bulk sms and afterwards record the participants’ responses. Based on these responses, the arrow will transfer the campaign audience to the following campaign step.


Resources

Package with example implementation