Implement a custom flow for a campaign element
The functionality is relevant to Classic UI.
To implement the example:
- Create a custom campaign element. Read more >>>
- Create a flow schema for a campaign element. Read more >>>
- Create a flow property panel for a campaign element. Read more >>>
- Create an executable element for the campaign element flow. Read more >>>
- Implement the business logic of the flow for the campaign element. Read more >>>
- Implement the flow logic when the source is changed. Read more >>>
- Connect the flow logic. Read more >>>
Create a custom flow for the custom Test SMS campaign element.
Implement the following flow options:
-
Omit the responces to the bulk SMS.
-
Take into account SMS responses. In this case, specify the responses to be taken into account.
-
Take into account SMS responses, regardless of their value.
1. Create a custom campaign element
For this example, create a custom Test SMS campaign element. Instructions: Implement a custom campaign element.
2. Create a flow schema for a campaign element
Create the SMS sending status lookup
-
Create a package to implement the example. Instructions: Create a user-made package.
For this example, create the
sdkCreateArrowCampaignPackage
package. -
Create the object schema to implement the SMS sending status lookup. Instructions: Implement an object.
For this example, create the
UsrSmsSendingStatus
object schema. Use the schema properties as follows.Property
Property value
Code
UsrSmsSendingStatus
Title
SMS sending status
Parent object
BaseLookup
-
Publish the schema.
-
Register the SMS sending status lookup. Instructions: Adding a lookup based on an existing object (user documentation).
-
Add the lookup values. Instructions: Manage lookup values.
For this example, add the following lookup values:
- Delivered
- Canceled
- Error while receiving
Create SMS recipient object.
-
Create an object schema. Instructions: Implement an object.
For this example, create the
UsrSmsRecipient
object schema. Use the schema properties as follows.Property
Property value
Code
UsrSmsRecipient
Title
SMS recipient
Parent object
BaseEntity
-
Add new columns to the object schema.
-
Click → Text → Text (50 characters) in the context menu of the object structure's Columns node.
-
The column properties are as follows.
Property
Property value
Code
UsrPhoneNumber
Title
Phone number
-
Repeat step 1-2 for the next three columns.
Column
Property
Property value
SMS text
Data type
Text (50 characters)
Code
UsrSmsText
Title
SMS text
SMS receiver
Data type
Lookup
Code
UsrContactLookup
Title
Contact
Lookup
Contact
SMS sending status
Data type
Lookup
Code
UsrSmsSendingStatusLookup
Title
SMS sending status
Lookup
UsrSmsSendingStatus
-
-
Publish the schema.
Create SMS responses object.
-
Create an object schema. Instructions: Implement an object.
For this example, create the
UsrSmsResponses
object schema. Use the schema properties as follows.Property
Property value
Code
UsrSmsResponses
Title
SMS responses
Parent object
BaseEntity
-
Add new column to the object schema.
-
Click → Text → Text (50 characters) in the context menu of the object structure's Columns node.
-
Fill out the column properties.
Property
Property value
Code
UsrResponse
Title
Response
-
-
Publish the schema.
Create a flow for a campaign element.
-
Create a client module schema. Instructions: Implement a non-visual module.
For this example, use the client module properties as follows.
Property
Property value
Code
UsrProcessTestSmsConditionalTransitionSchema
Title
Element conditional transition schema
-
Add source code that implements the flow conditions.
UsrProcessTestSmsConditionalTransitionSchemadefine("UsrProcessTestSmsConditionalTransitionSchema", ["CampaignEnums", "UsrProcessTestSmsConditionalTransitionSchemaResources", "ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) {
Ext.define("Terrasoft.manager.UsrProcessTestSmsConditionalTransitionSchema", {
extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema",
alternateClassName: "Terrasoft.UsrProcessTestSmsConditionalTransitionSchema",
managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444",
mixins: {
parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement"
},
/* Full name of the class that corresponds to the current schema.
If you use the class in a simple package, specify typeName as follows */
typeName: "Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, Terrasoft.Configuration",
/* If you use the class in an assembly package, specify typeName as follows:
typeName: "Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, SomeAssemblyPackageName" */
/* Arrow element name for linking to campaign elements. */
connectionUserHandleName: "TestSmsConditionalTransition",
/* Arrow property bar name. */
editPageSchemaName: "UsrTestSmsConditionalTransitionPropertiesPage",
elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION,
/* Collection of responses by SMS mailing. */
testSmsResponseId: null,
/* An attribute that takes into account the response condition when translating contacts. */
isResponseBasedStart: false,
getSerializableProperties: function() {
var baseSerializableProperties = this.callParent(arguments);
/* Properties to serialize and pass to back-end part when saving. */
Ext.Array.push(baseSerializableProperties, ["testSmsResponseId", "isResponseBasedStart"]);
return baseSerializableProperties;
}
});
return Terrasoft.UsrProcessTestSmsConditionalTransitionSchema;
});
Where:
-
managerItemUId
is the unique value for the new element. -
typeName
is the name of the C# class that corresponds to the campaign element name. The class saves and reads the element properties from the schema properties.noteIf you use the class in a simple package, specify
typeName
as follows:"Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, Terrasoft.Configuration"
. If you use the class in an assembly package, specifytypeName
as follows:"Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, SomeAssemblyPackageName"
.
- Save the schema.
3. Create a flow property panel for a campaign element
-
Create a page view model schema. Instructions: View model schema.
For this example, use page view model properties as follows.
Property
Property value
Code
UsrTestSmsConditionalTransitionPropertiesPage
Title
Properties panel of the transition Test SMS element
Parent object
CampaignConditionalSequenceFlowPropertiesPage
-
Add the localizable strings that include the field names. Instructions: Add a localizable string.
Code
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
-
Set up the properties panel of the flow.
Schema property
Code
Description
attributes
ReactionModeEnum
List of participants
ReactionMode
Participants' responses
IsTestSmsDelivered
SMS delivery status
IsErrorWhileReceiving
Receives an error during SMS delivery
rules
ReactionConditionDecision
Handles the value of the
ReactionMode
attributemethods
getResponseConfig()
Generates the correspondence of the bulk SMS responses to the SMS sending status lookup. It is assumed that the lookup contains Delivered and Error while receiving values. These status IDs are used in the property panel code of the conditional flow from bulk SMS. The unique ID is contained in the corresponding column of the SMS sending status lookup row.
subscribeEvents()
Binds the handler to the event that changes the
ReactionMode
attribute valueonReactionModeLookupChanged()
Handles the
ReactionMode
attribute change eventinitParameters()
Initializes the
ViewModel
properties to display the page when openedcutString()
Cuts the string to the specified length and adds an ellipsis triplet at the end. Auxiliary method
initIsTestSmsDelivered()
Sets the Delivered status
initIsErrorWhileReceiving()
Sets the Error while receiving status
initTestSmsResponses()
Initiates the selected responses when the page is opened
getIds()
Retrieves an ID array from an incoming JSON parameter. Auxiliary method
saveValues()
Saves the response values and the setting whether to add response conditions or not
getTestSmsResponseId()
Gets serialized ID of selected responses
getQuestionCaption()
Substitutes the name of the item the arrow comes from
diff
ContentContainer
Page container
ReactionModeLabel
Title
ReactionMode
List
IsTestSmsDelivered
List element
IsErrorWhileReceiving
List element
UsrTestSmsConditionalTransitionPropertiesPagedefine("UsrTestSmsConditionalTransitionPropertiesPage", ["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 ellipsis 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 schema.
4. Create an executable element for the campaign element flow
-
Create a source code schema. Instructions: Implement the source code.
For this example, use source code schema properties as follows.
Property
Property value
Code
UsrTestSmsConditionalTransitionFlowElement
Title
Conditional transition of the Test SMS process element
-
Implement an executable element that collects responses of the bulk SMS.
To do this, specify the
ConditionalTransitionFlowElement
class as a parent class.UsrTestSmsConditionalTransitionFlowElementnamespace Terrasoft.Configuration
{
using System;
using System.Collections.Generic;
using System.Linq;
using Terrasoft.Common;
using Terrasoft.Core.DB;
public class UsrTestSmsConditionalTransitionFlowElement : 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("UsrSmsRecipient")
.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();
}
}
} -
Publish the schema.
5. Implement the business logic of the flow for the campaign element
-
Create a source code schema. Instructions: Implement the source code.
For this example, use source code schema properties as follows.
Property
Property value
Code
UsrTestSmsConditionalTransitionElement
Title
Transition the Test SMS element
-
Implement the back-end business logic of a flow.
To do this, specify the
ConditionalSequenceFlowElement
class as a parent class.UsrTestSmsConditionalTransitionElementnamespace 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 UsrTestSmsConditionalTransitionElement : ConditionalSequenceFlowElement
{
private const string TestSmsResponseIdPropertyName = "TestSmsResponseId";
private const string IsResponseBasedStartPropertyName = "IsResponseBasedStart";
public UsrTestSmsConditionalTransitionElement() {}
public UsrTestSmsConditionalTransitionElement(UsrTestSmsConditionalTransitionElement source)
: this(source, null, null) {}
public UsrTestSmsConditionalTransitionElement(UsrTestSmsConditionalTransitionElement 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 UsrTestSmsConditionalTransitionFlowElement type. */
public override ProcessFlowElement CreateProcessFlowElement(UserConnection userConnection) {
var sourceElement = SourceRef as TestSmsElement;
var executableElement = new UsrTestSmsConditionalTransitionFlowElement {
UserConnection = userConnection,
TestSmsResponses = TestSmsResponseId,
PhoneNumber = sourceElement.PhoneNumber,
SmsText = sourceElement.SmsText
};
InitializeCampaignProcessFlowElement(executableElement);
InitializeCampaignTransitionFlowElement(executableElement);
InitializeConditionalTransitionFlowElement(executableElement);
return executableElement;
}
}
} -
Publish the schema.
6. Implement the flow logic when the source is changed
-
Create a client module schema. Instructions: Implement a non-visual module.
For this example, use the client module properties as follows.
Property
Property value
Code
UsrTestSmsCampaignConnectorManager
Title
Test SMS campaign connector manager
-
Implement the flow logic. To do this, add source code to the JS schema property.
UsrTestSmsCampaignConnectorManagerdefine("UsrTestSmsCampaignConnectorManager", [], function() {
Ext.define("Terrasoft.UsrTestSmsCampaignConnectorManager", {
/* 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("UsrTestSmsElementSchema",
"Terrasoft.UsrProcessTestSmsConditionalTransitionSchema");
},
/* 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 === "UsrProcessTestSmsConditionalTransitionSchema") {
/* 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 schema.
7. Connect the flow logic
- Create a replacing view model. Instructions: Implement a replacing module.
For this example, use replacing view model schema properties as follows.
Property | Property value |
---|---|
Code | BootstrapModulesV2 |
Title | BootstrapModulesV2 |
Parent object | BootstrapModulesV2 |
-
Add source code to the JS schema section.
BootstrapModulesV2/* Set the previously created UsrTestSmsCampaignConnectorManager module as a dependency. */
define("BootstrapModulesV2", ["UsrTestSmsCampaignConnectorManager","UsrProcessTestSmsConditionalTransitionSchema"], function() {});
require(["UsrTestSmsCampaignConnectorManager","UsrProcessTestSmsConditionalTransitionSchema"]); -
Save the schema.
View the result
-
Open the Marketing workspace.
-
Create a campaign. Instructions Create a Campaign.
-
Add the Test SMS element to the Campaign Designer canvas.
-
Add any other element (for example, Exit from campaign) to the Campaign Designer canvas.
-
Add a flow from the Test SMS element to the Exit from campaign element of the campaign.
The example displays the transition properties panel. View the result >>>
In the actual implementation of this case, we recommend to create a Bulk SMS object schema.
The UsrTestSmsElement
and UsrTestSmsConditionalTransitionElement
objects will contain the Id of this object and not the SmsText
, PhoneNumber
, etc. fields.
The UsrTestSmsCampaignProcessElement
executed element in the Execute()
method will 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 flow will transfer the campaign audience to the following campaign step.
Source code
- UsrProcessTestSms ConditionalTransitionSchema
- UsrTestSmsConditional TransitionPropertiesPage
- UsrTestSmsConditional TransitionFlowElement
- UsrTestSmsConditional TransitionElement
- UsrTestSmsCampaign ConnectorManager
- BootstrapModulesV2
define("UsrProcessTestSmsConditionalTransitionSchema", ["CampaignEnums", "UsrProcessTestSmsConditionalTransitionSchemaResources", "ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) {
Ext.define("Terrasoft.manager.UsrProcessTestSmsConditionalTransitionSchema", {
extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema",
alternateClassName: "Terrasoft.UsrProcessTestSmsConditionalTransitionSchema",
managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444",
mixins: {
parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement"
},
/* Full name of the class that corresponds to the current schema.
Use following typeName if the class is in the simple package. */
typeName: "Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, Terrasoft.Configuration",
/* If the class is in the assembly package use
typeName: "Terrasoft.Configuration.UsrTestSmsConditionalTransitionElement, SomeAssemblyPackageName" */
/* Arrow element name for linking to campaign elements. */
connectionUserHandleName: "TestSmsConditionalTransition",
/* Arrow property bar name. */
editPageSchemaName: "UsrTestSmsConditionalTransitionPropertiesPage",
elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION,
/* Collection of responses by SMS mailing. */
testSmsResponseId: null,
/* An attribute that takes into account the response condition when translating contacts. */
isResponseBasedStart: false,
getSerializableProperties: function() {
var baseSerializableProperties = this.callParent(arguments);
/* Properties to serialize and pass to back-end part when saving. */
Ext.Array.push(baseSerializableProperties, ["testSmsResponseId", "isResponseBasedStart"]);
return baseSerializableProperties;
}
});
return Terrasoft.UsrProcessTestSmsConditionalTransitionSchema;
});
define("UsrTestSmsConditionalTransitionPropertiesPage", ["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 ellipsis 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*/
};
});
namespace Terrasoft.Configuration
{
using System;
using System.Collections.Generic;
using System.Linq;
using Terrasoft.Common;
using Terrasoft.Core.DB;
public class UsrTestSmsConditionalTransitionFlowElement : 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("UsrSmsRecipient")
.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();
}
}
}
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 UsrTestSmsConditionalTransitionElement : ConditionalSequenceFlowElement
{
private const string TestSmsResponseIdPropertyName = "TestSmsResponseId";
private const string IsResponseBasedStartPropertyName = "IsResponseBasedStart";
public UsrTestSmsConditionalTransitionElement() {}
public UsrTestSmsConditionalTransitionElement(UsrTestSmsConditionalTransitionElement source)
: this(source, null, null) {}
public UsrTestSmsConditionalTransitionElement(UsrTestSmsConditionalTransitionElement 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 UsrTestSmsConditionalTransitionFlowElement type. */
public override ProcessFlowElement CreateProcessFlowElement(UserConnection userConnection) {
var sourceElement = SourceRef as TestSmsElement;
var executableElement = new UsrTestSmsConditionalTransitionFlowElement {
UserConnection = userConnection,
TestSmsResponses = TestSmsResponseId,
PhoneNumber = sourceElement.PhoneNumber,
SmsText = sourceElement.SmsText
};
InitializeCampaignProcessFlowElement(executableElement);
InitializeCampaignTransitionFlowElement(executableElement);
InitializeConditionalTransitionFlowElement(executableElement);
return executableElement;
}
}
}
define("UsrTestSmsCampaignConnectorManager", [], function() {
Ext.define("Terrasoft.UsrTestSmsCampaignConnectorManager", {
/* 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("UsrTestSmsElementSchema",
"Terrasoft.UsrProcessTestSmsConditionalTransitionSchema");
},
/* 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 === "UsrProcessTestSmsConditionalTransitionSchema") {
/* 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;
}
}
});
});
/* Set the previously created UsrTestSmsCampaignConnectorManager module as a dependency. */
define("BootstrapModulesV2", ["UsrTestSmsCampaignConnectorManager","UsrProcessTestSmsConditionalTransitionSchema"], function() {});
require(["UsrTestSmsCampaignConnectorManager","UsrProcessTestSmsConditionalTransitionSchema"]);