Skip to main content
Version: 8.3

Implement a custom flow for a campaign element

Level: advanced

To implement the example:

  1. Implement a custom campaign element. Read more >>>
  2. Create a schema for the campaign element flow. Read more >>>
  3. Create a property panel for the campaign element flow. Read more >>>
  4. Create an executable element for the campaign element flow. Read more >>>
  5. Implement the business logic of the campaign element flow. Read more >>>
  6. Extend campaign connector manager. Read more >>>
  7. Connect the business logic of the campaign element flow. Read more >>>
Example

Create a custom conditional flow for the custom SMS campaign element. The flow must execute the following actions:

  • Omit the responses to the bulk SMS.

  • Handle delivered SMS responses and SMS responses whose receiving ends with error.

  • Receive SMS responses, regardless of their value.

1. Implement a custom campaign element

Instructions: Implement a custom campaign element.

2. Create a schema for the campaign element flow

1. Create the SMS sending status lookup

  1. Open the Configuration section. Instructions: Open the Configuration section.

  2. Create a sdkCustomCampaignElementFlow package. Instructions: Create a user-made package using Configuration section.

  3. Change the current package to sdkCustomCampaignElementFlow. Instructions: Change the current package.

  4. Click AddObject.

  5. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    General property block

    Code

    UsrSmsSendingStatus

    Title

    SMS sending status

    Inheritance property block

    Parent object

    BaseLookup

  6. Click Save and publish.

  7. Register the lookup.

    1. Click in the top right → System setupLookups.

    2. Create a lookup.

      1. Click New lookup.

      2. Fill out the lookup properties.

        Property

        Property value

        Name

        SMS sending status

        Object

        SMS sending status

      3. Click Save.

    3. Fill out the lookup.

      For this example, add the following lookup values:

      • Delivered
      • Canceled
      • Error while receiving

2. Create SMS recipient object

  1. Open the Configuration section. Instructions: Open the Configuration section.

  2. Select the sdkCustomCampaignElementFlow package.

  3. Click AddObject.

  4. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    General property block

    Code

    UsrSmsRecipient

    Title

    Sms recipient

    Inheritance property block

    Parent object

    BaseEntity

  5. Add columns.

    For this example, add the following columns:

    • column that contains the phone number
    • column that contains the SMS text
    • column that contains the SMS receiver
    • column that contains the SMS sending status

    To do this:

    1. Go to the Columns node's context menu.

    2. Click → select column type → fill out the column properties.

      Column

      Column type

      Property

      Property value

      Column that contains the phone number

      Text (50 characters)

      Code

      UsrPhoneNumber

      Title

      Phone

      Column that contains the SMS text

      Text (50 characters)

      Code

      UsrSmsText

      Title

      SMS text

      Column that contains the SMS receiver

      Lookup

      Code

      UsrContactLookup

      Title

      Contact

      Lookup

      Contact

      Column that contains the SMS sending status

      Lookup

      Code

      UsrSmsSendingStatusLookup

      Title

      SMS sending status

      Lookup

      UsrSmsSendingStatus

  6. Click Save and publish.

3. Create SMS responses object

  1. Click AddObject.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    General property block

    Code

    UsrSmsResponses

    Title

    SMS responses

    Inheritance property block

    Parent object

    BaseEntity

  3. Add columns.

    For this example, add the column that contains the SMS response. To do this:

    1. Go to the Columns node's context menu.

    2. Click → select column type → fill out the column properties.

      Column

      Column type

      Property

      Property value

      Column that contains the SMS response

      Text (50 characters)

      Code

      UsrResponse

      Title

      Response

  4. Click Save and publish.

4. Create a flow for a campaign element

  1. Click AddModule.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Code

    UsrProcessSmsConditionalTransitionSchema

    Title

    Element conditional transition schema

  3. Add source code that implements the flow conditions.

    UsrProcessSmsConditionalTransitionSchema
    define("UsrProcessSmsConditionalTransitionSchema", ["CampaignEnums",
    "UsrProcessSmsConditionalTransitionSchemaResources",
    "ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) {
    Ext.define("Terrasoft.manager.UsrProcessSmsConditionalTransitionSchema", {
    /* Parent schema. */
    extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema",
    /* Alternative class name. */
    alternateClassName: "Terrasoft.UsrProcessSmsConditionalTransitionSchema",
    /* ID of the new campaign element. */
    managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444",
    /* Mixins that extend functionality. */
    mixins: {
    parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement"
    },
    /* Full name of the class that implements the campaign element logic.
    This class saves and reads the element properties from the schema
    properties. Since the class is placed in the regular package, use the
    "Terrasoft.Configuration.UsrSmsConditionalTransitionElement,
    Terrasoft.Configuration" typeName. If the class is placed in the
    assembly package, use
    typeName: "Terrasoft.Configuration.UsrSmsConditionalTransitionElement,
    SomeAssemblyPackageName" */
    typeName: "Terrasoft.Configuration.UsrSmsConditionalTransitionElement," +
    "Terrasoft.Configuration",
    /* Arrow element name used for linking campaign elements. */
    connectionUserHandleName: "SmsConditionalTransition",
    /* Schema name of the edit page. */
    editPageSchemaName: "UsrSmsConditionalTransitionPropertiesPanel",
    /* Element type. */
    elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION,
    /* ID of the SMS response option linked to the transition. */
    smsResponseId: null,
    /* Define whether the transition is based on a specific SMS
    response condition. */
    isResponseBasedStart: false,
    /* Expand the properties for serialization. */
    getSerializableProperties: function() {
    var baseSerializableProperties = this.callParent(arguments);
    Ext.Array.push(baseSerializableProperties, ["smsResponseId",
    "isResponseBasedStart"]);
    return baseSerializableProperties;
    }
    });
    return Terrasoft.UsrProcessSmsConditionalTransitionSchema;
    }
    );
  4. Click Save.

5. Add the campaign element frow to the Campaign Designer

  1. Click AddReplacing view model.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Parent object

    CampaignElementSchemaManagerEx

  3. Add the source code.

    CampaignElementSchemaManagerEx
    require(["CampaignElementSchemaManager", "UsrProcessSmsConditionalTransitionSchema"],
    function() {
    /* Register the "UsrProcessSmsConditionalTransitionSchema" in the Campaign Designer. */
    var coreElementClassNames = Terrasoft.CampaignElementSchemaManager.coreElementClassNames;

    /* Add the custom schema to the list of available schemas. */
    coreElementClassNames.push({
    itemType: "Terrasoft.UsrProcessSmsConditionalTransitionSchema"
    });
    }
    );
  4. Click Save.

3. Create a property panel for the campaign element flow

  1. Click AddPage view model.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Code

    UsrSmsConditionalTransitionPropertiesPanel

    Title

    Properties panel for the custom flow

    Parent object

    CampaignConditionalSequenceFlowPropertiesPage

  3. Add the localizable strings that include the field names.

    1. Create a localizable string. Instructions: Add a localizable string.

    2. Fill out the localizable string parameters.

      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

      IsSmsDelivered

      SMS is delivered

      IsErrorWhileReceiving

      Error while receiving

  4. Implement the business logic of the campaign element flow.

    UsrSmsConditionalTransitionPropertiesPanel
    define("UsrSmsConditionalTransitionPropertiesPanel", ["BusinessRuleModule",
    "LookupUtilities"], function(BusinessRuleModule, LookupUtilities) {
    return {
    messages: {},
    /* Schema attributes. */
    attributes: {
    /* Attribute that stores data about list of participants. */
    "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"
    }
    }
    },
    /* Attribute that stores data about participants' responses. */
    "ReactionMode": {
    "dataValueType": this.Terrasoft.DataValueType.LOOKUP,
    "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN,
    "isRequired": true
    },
    /* Attribute that stores data about SMS delivery status. */
    "IsSmsDelivered": {
    "dataValueType": this.Terrasoft.DataValueType.BOOLEAN,
    "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN
    },
    /* Attribute that stores data about error occurred while
    receiving SMS. */
    "IsErrorWhileReceiving": {
    "dataValueType": this.Terrasoft.DataValueType.BOOLEAN,
    "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN
    }
    },
    /* Schema business rules. */
    rules: {
    /* Make the "ReactionConditionDecision" business rule
    required when the "ReactionMode" attribute is equal "1." */
    "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"
    }
    }]
    }
    }
    },
    /* Schema methods. */
    methods: {
    /* Return mapping of attributes to the "SMS response
    type" ("SmsResponseType" code) lookup records. */
    getResponseConfig: function() {
    return {
    "IsSmsDelivered": "5a9f08a4-c7f2-4dc8-9940-13ba1b2762cf",
    "IsErrorWhileReceiving": "4a790626-bfbb-4ca7-a9f2-595000262561"
    };
    },
    /* Bind the handler to the event that changes the "ReactionMode"
    attribute value. */
    subscribeEvents: function() {
    this.callParent(arguments);
    /* Subscribe to changes of the "ReactionMode" attribute
    value. */
    this.on(
    "change:ReactionMode",
    this.onReactionModeLookupChanged,
    this
    );
    },

    /* Handle the ReactionMode attribute change event. */
    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);
    }
    },

    /* Initialize attributes for displaying the page when opened. */
    initParameters: function(element) {
    this.callParent(arguments);
    var isResponseBasedStart = element.isResponseBasedStart;
    this.initReactionMode(isResponseBasedStart);
    this.initSmsResponses(element.smsResponseId);
    },

    /* Cut the string to the specified length and append an
    ellipsis at the end. Auxiliary method. */
    cutString: function(strValue, strLength) {
    var ellipsis = Ext.String.ellipsis(
    strValue.substring(strLength),
    0
    );
    return strValue.substring(0, strLength) + ellipsis;
    },

    /* Set the status value to "Delivered." */
    initIsSmsDelivered: function(value) {
    if (value === undefined) {
    value = this.get("IsSmsDelivered");
    }
    this.set("IsSmsDelivered", value);
    },

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

    /* Initialize the selected responses when opening the page. */
    initSmsResponses: 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);
    },

    /* Initialize the "ReactionMode" attribute based on
    "isResponseBasedStart." */
    initReactionMode: function(value) {
    var isDefault = !value;
    this.setLookupValue(
    isDefault,
    "ReactionMode",
    "WithCondition",
    this
    );
    },

    /* Parse JSON array of IDs. Return "[]" on error. */
    getIds: function(idsJson) {
    if (idsJson) {
    try {
    var ids = JSON.parse(idsJson);
    if (this.Ext.isArray(ids)) {
    return ids;
    }
    } catch (error) {
    return [];
    }
    }
    return [];
    },

    /* Populate "ReactionMode" attribute list from "ReactionModeEnum". */
    onPrepareReactionModeList: function(filter, list) {
    this.prepareList("ReactionModeEnum", list, this);
    },

    /* Save UI values back to the process element before save. */
    saveValues: function() {
    this.callParent(arguments);
    var element = this.get("ProcessElement");
    var isResponseBasedStart = this.getIsReactionModeWithConditions();
    element.isResponseBasedStart = isResponseBasedStart;
    element.smsResponseId = this.getSmsResponseId(isResponseBasedStart);
    },

    /* Serialize selected response IDs to JSON.*/
    getSmsResponseId: 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);
    },

    /* Return lookup underlying ID or null. */
    getLookupValue: function(parameterName) {
    var value = this.get(parameterName);
    return value ? value.value : null;
    },

    /* Help context code for the property page. */
    getContextHelpCode: function() {
    return "CampaignConditionalSequenceFlow";
    },

    /* Return "true" if "ReactionMode" is set to "1." */
    getIsReactionModeWithConditions: function() {
    return this.isLookupValueEqual("ReactionMode", "1", this);
    },

    /* Return source element of the transition if available. */
    getSourceElement: function() {
    var flowElement = this.get("ProcessElement");
    if (flowElement) {
    return flowElement.findSourceElement();
    }
    return null;
    },
    /* Format question caption including the source element name. */
    getQuestionCaption: function() {
    var caption = this.get("Resources.Strings.ReactionModeCaption");
    caption = this.Ext.String.format(
    caption,
    this.getSourceElement().getCaption()
    );
    return caption;
    }
    },
    /* The schema view description. */
    diff: /**SCHEMA_DIFF*/[
    /* Page 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 label. */
    {
    "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"]
    }
    }
    },
    /* "ReactionMode" 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"]
    }
    },
    /* "IsSmsDelivered" checkbox.*/
    {
    "operation": "insert",
    "parentName": "ReactionContainer",
    "propertyName": "items",
    "name": "IsSmsDelivered",
    "values":
    {
    "wrapClass": ["t-checkbox-control"],
    "visible":
    {
    "bindTo": "ReactionMode",
    "bindConfig":
    {
    converter: "getIsReactionModeWithConditions"
    }
    },
    "caption":
    {
    "bindTo": "Resources.Strings.IsSmsDelivered"
    },
    "layout":
    {
    "column": 0,
    "row": 2,
    "colSpan": 22
    }
    }
    },
    /* "IsErrorWhileReceiving" checkbox.*/
    {
    "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*/
    };
    }
    );
  5. Click Save.

4. Create an executable element for the campaign element flow

  1. Click AddSource code.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Code

    UsrSmsConditionalTransitionFlowElement

    Title

    Conditional transition of the SMS process element

  3. Implement an executable element that collects responses of the bulk SMS.

    1. Specify the ConditionalTransitionFlowElement class as a parent class.
    2. Override the CreateQuery() method.
    UsrSmsConditionalTransitionFlowElement
    namespace Terrasoft.Configuration
    {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Terrasoft.Common;
    using Terrasoft.Core.DB;

    /* Define a custom element flow. */
    public class UsrSmsConditionalTransitionFlowElement
    : ConditionalTransitionFlowElement
    {

    /* Assign the "SmsText" attribute value in the class. */
    public string SmsText {
    get;
    set;
    }

    /* Assign the "PhoneNumber" attribute value in the class. */
    public string PhoneNumber {
    get;
    set;
    }

    /* Assign the collection of SMS response IDs in the class. */
    public IEnumerable<Guid> SmsResponses {
    get;
    set;
    }

    /* Extend the base "TransitionQuery" with participant responses
    filter. */
    private void ExtendWithResponses() {
    TransitionQuery.CheckArgumentNull("TransitionQuery");
    if (SmsResponses.Any()) {
    Query responseSelect = GetSelectByParticipantResponses();
    TransitionQuery.And("ContactId").In(responseSelect);
    }
    }

    /* Build a subquery selecting "ContactId" from "UsrSmsRecipient"
    filtered by "SmsText," "PhoneNumber," and "SmsResponseId." */
    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("SmsResponseId")
    .In(Column.Parameters(SmsResponses)) as Select;
    responseSelect.SpecifyNoLockHints(true);
    return responseSelect;
    }

    /* Extend base query with response filters. */
    protected override void CreateQuery() {
    base.CreateQuery();
    ExtendWithResponses();
    }
    }
    }
  4. Click Save and publish.

5. Implement the business logic of the campaign element flow

  1. Click AddSource code.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Code

    UsrSmsConditionalTransitionElement

    Title

    Transition the SMS element

  3. Specify the ConditionalSequenceFlowElement class as a parent class.

  4. Override the methods.

    • ApplyMetaDataValue(DataReader reader)
    • WriteMetaData(DataWriter writer)
    • Clone()
    • Copy(Dictionary<Guid, Guid> dictToRebind, Core.Campaign.CampaignSchema parentSchema)
    • CreateProcessFlowElement(UserConnection userConnection)
    UsrSmsConditionalTransitionElement
    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 = "SmsResponseId",
    UsageType = DesignModeUsageType.NotVisible,
    MetaPropertyName = SmsResponseIdPropertyName)]

    [DesignModeProperty(
    Name = "IsResponseBasedStart",
    UsageType = DesignModeUsageType.Advanced,
    MetaPropertyName = IsResponseBasedStartPropertyName)]

    public class UsrSmsConditionalTransitionElement
    : ConditionalSequenceFlowElement
    {

    /* Constants for property names used in metadata serialization. */
    private const string SmsResponseIdPropertyName = "SmsResponseId";
    private const string IsResponseBasedStartPropertyName = "IsResponseBasedStart";

    /* Default constructor. */
    public UsrSmsConditionalTransitionElement() {}

    /* Copy constructor without rebind and schema. */
    public UsrSmsConditionalTransitionElement(
    UsrSmsConditionalTransitionElement source)
    : this(source, null, null)
    {}

    /* Copy constructors for cloning the element in schema operations. */
    public UsrSmsConditionalTransitionElement(
    UsrSmsConditionalTransitionElement source,
    Dictionary<Guid, Guid> dictToRebind,
    Core.Campaign.CampaignSchema parentSchema)
    : base(source, dictToRebind, parentSchema) {
    IsResponseBasedStart = source.IsResponseBasedStart;
    _smsResponseIdJson = JsonConvert.SerializeObject(
    source.SmsResponseId
    );
    }

    /* Store "SmsResponseId" in serialized JSON format. */
    private string _smsResponseIdJson;

    /* Internal helper property, return the collection of response IDs. */
    private IEnumerable<Guid> Responses {
    get {
    return SmsResponseId;
    }
    }

    /* Meta property that stores a collection of SMS response IDs. */
    [MetaTypeProperty("{DC597899-B831-458A-A58E-FB43B1E266AC}")]
    public IEnumerable<Guid> SmsResponseId {
    get {
    return !string.IsNullOrWhiteSpace(_smsResponseIdJson)
    ? JsonConvert.DeserializeObject<IEnumerable<Guid>>(_smsResponseIdJson)
    : Enumerable.Empty<Guid>();
    }
    }

    /* Meta property that defines if the transition starts based on a
    response. */
    [MetaTypeProperty("{3FFA4EA0-62CC-49A8-91FF-4096AEC561F6}",
    IsExtraProperty = true, IsUserProperty = true)]
    public virtual bool IsResponseBasedStart {
    get;
    set;
    }

    /* Read element metadata from the schema definition during
    deserialization. */
    protected override void ApplyMetaDataValue(DataReader reader) {
    base.ApplyMetaDataValue(reader);
    switch (reader.CurrentName) {
    case SmsResponseIdPropertyName:
    _smsResponseIdJson = reader.GetValue<string>();
    break;
    case IsResponseBasedStartPropertyName:
    IsResponseBasedStart = reader.GetBoolValue();
    break;
    default:
    break;
    }
    }

    /* Write element metadata to the schema definition during serialization. */
    public override void WriteMetaData(DataWriter writer) {
    base.WriteMetaData(writer);
    writer.WriteValue(
    IsResponseBasedStartPropertyName,
    IsResponseBasedStart,
    false
    );
    writer.WriteValue(
    SmsResponseIdPropertyName,
    _smsResponseIdJson,
    null
    );
    }

    /* Create a clone of the schema element. */
    public override object Clone() {
    return new UsrSmsConditionalTransitionElement(this);
    }

    /* Create a copy of the element using rebind support for schema references. */
    public override object Copy(
    Dictionary<Guid, Guid> dictToRebind,
    Core.Campaign.CampaignSchema parentSchema
    ) {
    return new UsrSmsConditionalTransitionElement(
    this,
    dictToRebind,
    parentSchema
    );
    }

    /* Create the runtime process flow element that will be executed in
    the campaign. Pass metadata values to the executable process element. */
    public override ProcessFlowElement CreateProcessFlowElement(
    UserConnection userConnection
    ) {
    var sourceElement = SourceRef as UsrSmsElement;
    var executableElement = new UsrSmsConditionalTransitionFlowElement {
    UserConnection = userConnection,
    SmsResponses = SmsResponseId,
    PhoneNumber = sourceElement.PhoneNumber,
    SmsText = sourceElement.SmsText
    };
    InitializeCampaignProcessFlowElement(executableElement);
    InitializeCampaignTransitionFlowElement(executableElement);
    InitializeConditionalTransitionFlowElement(executableElement);
    return executableElement;
    }
    }
    }
  5. Click Save and publish.

6. Extend campaign connector manager

  1. Click AddModule.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Code

    UsrSmsCampaignConnectorManager

    Title

    SMS campaign connector manager

  3. Add the source code.

    UsrSmsCampaignConnectorManager
    define("UsrSmsCampaignConnectorManager", [], function() {
    Ext.define("Terrasoft.UsrSmsCampaignConnectorManager", {
    /* Override the standard "CampaignConnectorManager" module to extend
    its logic with custom SMS connector handling. */
    override: "Terrasoft.CampaignConnectorManager",

    /* Initialize mapping between the campaign element (arrow source) and
    the corresponding transition schema (arrow type). Add a mapping for
    the custom SMS element. */
    initMappingCollection: function() {
    this.callParent(arguments);
    this.connectorTypesMappingCollection.addIfNotExists(
    "UsrSmsElement",
    "Terrasoft.UsrProcessSmsConditionalTransitionSchema"
    );
    },

    /* Virtual method override. Called before the arrow is replaced with
    a new type. Can be used to execute additional preprocessing logic
    when the connector type changes. */
    additionalBeforeChange: function(prevTransition, sourceItem, targetItem) {
    /* Implement custom preprocessing logic. */
    },

    /* Virtual method override. Populate additional properties of the
    new arrow based on the properties of the previous arrow. */
    fillAdditionalProperties: function(prevElement, newElement) {
    if (newElement.getTypeInfo().typeName === "UsrProcessSmsConditionalTransitionSchema") {
    /* Copy the configured SMS responses from the previous arrow,
    if they exist. */
    newElement.smsResponseId = prevElement.smsResponseId
    ? prevElement.smsResponseId
    : null;
    /* Copy the flag that defines whether the transition is response-based. */
    newElement.isResponseBasedStart = prevElement.isResponseBasedStart
    ? prevElement.isResponseBasedStart
    : false;
    }
    }
    });
    });
  4. Click Save.

7. Connect the business logic of the campaign element flow

  1. Click AddReplacing view model.

  2. Fill out the schema properties.

    For this example, use the following schema properties.

    Property

    Property value

    Parent object

    BootstrapModulesV2

  3. Add the source code.

    BootstrapModulesV2
    /* Define a replacing view model schema named "BootstrapModulesV2." This schema
    is used to include additional client modules that must be loaded during the
    app startup process. Custom SMS campaign connector manager and the custom
    conditional transition schema are added as dependencies.
    "UsrSmsCampaignConnectorManager" is the previously created custom connector
    manager that overrides "CampaignConnectorManager" and implements SMS-specific
    flow logic.
    "UsrProcessSmsConditionalTransitionSchema" is the custom schema that defines
    the conditional transition for the SMS campaign element.*/
    define("BootstrapModulesV2", ["UsrSmsCampaignConnectorManager",
    "UsrProcessSmsConditionalTransitionSchema"], function() {});
    /* Explicitly require the same dependencies to ensure they are loaded at
    runtime when the app starts. */
    require(["UsrSmsCampaignConnectorManager","UsrProcessSmsConditionalTransitionSchema"]);
  4. Click Save.

View the result

  1. Open the Campaigns section.
  2. Create a campaign that has arbitrary parameters. Instructions Add a campaign (user documentation).
  3. Click Edit campaign flow.
  4. Add the SMS element to the canvas.
  5. Add arbitrary elements to the canvas. For example, Exit from campaign.
  6. Add a flow between the SMS and Exit from campaign elements.

As a result, the flow executes the following actions:

If you need to use the current example in your instance, we recommend creating the Bulk SMS object schema instead of storing SMS details directly in campaign elements. The UsrTestSmsElement and UsrSmsConditionalTransitionElement object schemas should reference only the ID of the bulk SMS record, rather than fields such as SmsText or PhoneNumber. The UsrTestSmsCampaignProcessElement executed element should implement the audience registration logic in its Execute() method. For example, by adding contacts to the bulk SMS audience. A separate mechanism (or multiple mechanisms) must handle sending SMS messages and recording participant responses. Based on these responses, the campaign flow will transfer the audience to the next campaign step.


Source code

define("UsrProcessSmsConditionalTransitionSchema", ["CampaignEnums",
"UsrProcessSmsConditionalTransitionSchemaResources",
"ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) {
Ext.define("Terrasoft.manager.UsrProcessSmsConditionalTransitionSchema", {
/* Parent schema. */
extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema",
/* Alternative class name. */
alternateClassName: "Terrasoft.UsrProcessSmsConditionalTransitionSchema",
/* ID of the new campaign element. */
managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444",
/* Mixins that extend functionality. */
mixins: {
parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement"
},
/* Full name of the class that implements the campaign element logic.
This class saves and reads the element properties from the schema
properties. Since the class is placed in the regular package, use the
"Terrasoft.Configuration.UsrSmsConditionalTransitionElement,
Terrasoft.Configuration" typeName. If the class is placed in the
assembly package, use
typeName: "Terrasoft.Configuration.UsrSmsConditionalTransitionElement,
SomeAssemblyPackageName" */
typeName: "Terrasoft.Configuration.UsrSmsConditionalTransitionElement," +
"Terrasoft.Configuration",
/* Arrow element name used for linking campaign elements. */
connectionUserHandleName: "SmsConditionalTransition",
/* Schema name of the edit page. */
editPageSchemaName: "UsrSmsConditionalTransitionPropertiesPanel",
/* Element type. */
elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION,
/* ID of the SMS response option linked to the transition. */
smsResponseId: null,
/* Define whether the transition is based on a specific SMS
response condition. */
isResponseBasedStart: false,
/* Expand the properties for serialization. */
getSerializableProperties: function() {
var baseSerializableProperties = this.callParent(arguments);
Ext.Array.push(baseSerializableProperties, ["smsResponseId",
"isResponseBasedStart"]);
return baseSerializableProperties;
}
});
return Terrasoft.UsrProcessSmsConditionalTransitionSchema;
}
);

Resources

Package with example implementation (campaign element)

Package with example implementation (flow for campaign element)