Add an activity type to the Marketing Automation UI

This topic describes how to add a custom activity type to the Marketing Automation UI.

Prepare the plugin project

To be able to add an activity type to the Marketing Automation UI, you prepare a plugin project (an NPM project) and register it with the Marketing Automation application.

To prepare the plugin project, you:

  • Create an NPM project and install the @sitecore/ma-core package
  • Add a plugin class
  • Configure Angular compiler
  • Bundle the plugin artifacts with Webpack
  • Register the plugin

Create an NPM project and install the @sitecore/ma-core package

To create the NPM project and install the @sitecore/ma-core package:

  1. To create an NPM project, run npm init and then set the values for your package.json field.
  2. To install the @sitecore/ma-core package, run npm install and specify the fully qualified path of the package (Website\sitecore\shell\client\Applications\MarketingAutomation\packages\ma-core). You must install the peer dependencies of the @sitecore/ma-core package. After you install the package, NPM shows a warning for each peer dependency that is missing. The following packages and versions are required:
  • @angular/core: 4.4.6
  • @angular/http: 4.4.6
  • @ngx-translate/core: 7.2.0
  • rxjs: 5.5.2

Note

For more information about NPM projects, refer to https://docs.npmjs.com/cli/init.

Add a plugin class

The plugin class is the plugin’s façade that contains the metadata that the Marketing Automation application uses to discover and register the activities that are configured in the plugin.

To add a plugin class:

  • Create a class and decorate it with the plugin decorator:
@Plugin({
    activityDefinitions: [ ]
})
export default class SamplePlugin {}

The plugin decorator has one property, activityDefinitions, which is an array of ActivityDefinition objects. The ActivityDefinition class has the following required properties:

Property Type Description
id string The ID of the sitecore activity item that exists in the master database under sitecore/System/Settings/Analytics/Marketing Automation/Activity Types item.
activity ItemBase The activity class implementation. It must be a class that inherits the SingleItem, FinalItem, ConditionItem, or DecisionPointItem.
editorComponenet EditorBase The activity editor class. It must be referenced from the codegen folder.
editorModuleFactory NgModuleFactory<any> A generated code artifact. After you run the angular compiler, it generates a factory for each module.

Important

The plugin class must be the :doc:`default export of the typescript module <https://www.typescriptlang.org/docs/handbook/modules.html#default-exports>`_ (the class must marked export default). This is necessary for it to be discovered by the Marketing Automation UI.

Configure Angular compiler

All the activity editors and the angular artifacts (components, directives, services, and pipes) must be compiled by the Angular AoT compiler. However, the Marketing Automation application does not come with the Angular JiT compiler and therefore you must configure the AoT compiler to compile your activity editors. To configure an angular compiler:

  1. Install the @angular/compiler-cli package and all its dependencies. You can find the package in the NPM registry (https://www.npmjs.com/).
  2. Add a tsconfig.aot.json file to the src folder:
{
    "compilerOptions": {
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "removeComments": true,
        "sourceMap": true,
        "outDir": "../codegen",
        "rootDir": "./",
        "declaration": true,
        "lib": [
            "es2016",
            "dom"
        ]
    },
    "angularCompilerOptions": {
        "genDir": "../codegen",
        "skipMetadataEmit": true,
        "generateCodeForLibraries": true
    }
}
  1. To run the angular compiler, create a script in the package.json file. The script that compiles the angular application must be as follows:
"scripts": {
"dev": "ngc -p ./src/tsconfig.aot.json"
}

The script runs the angular compiler that generates a factory for each angular module. This factory is required to register the editor modules in the plugin decorator.

Bundle plugin artifacts with Webpack

You use Webpack to bundle your plugin artifacts together into a single output file and the plugin class, which you implemented earlier, as an entry point.

Important

The Marketing Automation application uses the module loader SystemJS internally to load plugins. This means that the sample plugin module format can be any of the formats supported by SystemJS. This example uses the UMD format (universal module format) as defined in the libraryTarget property.

You must set @sitecore/ma-core, @angular/core, and @ngx-translate/core as external libraries. These libraries are provided by the Marketing Automation application at runtime and if they are not configured as externals, they end up bundled along with the plugin artifacts and the plugin cannot load.

To configure Webpack:

  1. Install Webpack and ts-loader by running:
npm install webpack ts-loader --save-dev
  1. Add the webpack.config.js file in the root folder of the plugin, for example:
var path = require('path');

module.exports = {
    entry: './src/sample.plugin.ts',
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: 'ts-loader',
                exclude: path.resolve(__dirname, "node_modules")
            }
        ]
    },
    resolve: {
        extensions: [".ts", ".js"]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'sample.plugin.js',
        library: "samplePlugin",
        libraryTarget: "umd"
    },
    externals: [
        "@sitecore/ma-core",
        "@angular/core",
        "@ngx-translate/core"
    ]
};
  1. To run webpack, build a script to the package.json file:
"scripts": {
    "build": "webpack"
}

Register the plugin

You must register the plugin so that the Marketing Automation application can discover the plugin.

To register the plugin:

  1. In the Sitecore webroot under the /sitecore/shell/client/Applications/MarketingAutomation/plugins folder, deploy the javascript plugin bundle file.
  2. To patch the Sitecore configuration, add a plugin node that contains the relative path of the plugin bundle file:
<sitecore>
    <marketingAutomation>
        <pluginDescriptorsRepository>
            <plugins>
                <!-- Assuming that the plugin bundle file name is sample.plugin.js and is deployed directly under the plugins folder -->
                <plugin path="./plugins/sample.plugin.js" />
            </plugins>
        </pluginDescriptorsRepository>
    </marketingAutomation>
</sitecore>

Implement activities

Marketing automation has a set of base activity types:

  • SingleItem
  • FinalItem
  • ConditionItem
  • DecisionPointItem

You can implement your custom activities by inheriting from these base activity classes that are provided in the @sitecore/ma-core package.

SingleItem

The SingleItem activity has one ingoing path and one outgoing path and it has a title and a subtitle that are displayed when the parameters of the activity are defined. In an automation campaign, the activity can be placed before or after other SingleItem activities.

The supported CSS classes

CSS class Undefined Defined
marketing-action
other-element

The SingleItem activity implementation

import { SingleItem } from '@sitecore/ma-core';

export class SampleSingleItemActivity extends SingleItem {

    getVisual(): string {
        const subTitle = this.isDefined ? 'Count: ' + this.editorParams.count : '';
        const cssClass = this.isDefined ? '' : 'undefined';

        //CSS class can either be makreting-action or other-element
        return `
            <div class="marketing-action ${cssClass}">
                <span class="icon">
                    <img src="/~/icon/OfficeWhite/32x32/gearwheels.png" />
                </span>
                <p class="text with-subtitle" title="Sample single item">
                    Sample single item
                    <small class="subtitle" title="${subTitle}">${subTitle}</small>
                </p>
            </div>
        `;
    }

    get isDefined(): boolean {
        /*
        The editorParams propery value is the object that is serialized from the activity editor.
        If the activity is undefined the editorParams propery will evaluate to {}
        */
        return Boolean(this.editorParams.count);
    }
}

FinalItem

The FinalItem activity has one ingoing path and no outgoing path and it has a title and a subtitle that are displayed when the parameters of the activity are defined. The activity can only be placed before another FinalItem activity type.

The supported CSS classes

CSS class Undefined Defined
marketing-action
other-element

The FinalItem activity implementation

import { FinalItem } from '@sitecore/ma-core';

export class SampleFinalItemActivity extends FinalItem {

    getVisual(): string {
        const subTitle = this.isDefined ? 'Count: ' + this.editorParams.count : '';
        const cssClass = this.isDefined ? '' : 'undefined';

        return `
            <div class="other-element ${cssClass}">
                <span class="icon">
                    <img src="/~/icon/OfficeWhite/32x32/gearwheels.png" />
                </span>
                <p class="text with-subtitle" title="Sample final item">
                    Sample final item
                    <small class="subtitle" title="${subTitle}">${subTitle}</small>
                </p>
            </div>
        `;
    }

    get isDefined(): boolean {
        /*
        The editorParams propery value is the object that is serialized from the activity editor.
        If the activity is undefined the editorParams propery will evaluate to {}
        */
        return Boolean(this.editorParams.count);
    }
}

ConditionItem

The ConditionItem activity has one ingoing path and two outgoing paths and it has a title and a subtitle that are displayed when the parameters of the activity are defined. The activity can be placed either before, after, or beneath the Yes and No paths of other ConditionItem activity types.

The supported CSS classes

CSS class Undefined Defined
listener

The ConditionItem activity implementation

import { ConditionItem } from '@sitecore/ma-core';

export class SampleConditionItemActivity extends ConditionItem {

    getVisual(): string {
        const subTitle = this.isDefined ? 'Count: ' + this.editorParams.count : '';
        const cssClass = this.isDefined ? '' : 'undefined';

        return `
            <div class="listener ${cssClass}">
                <span class="icon">
                    <img src="/~/icon/OfficeWhite/32x32/gearwheels.png" />
                </span>
                <p class="text with-subtitle" title="Sample condition item">
                    Sample condition item
                    <small class="subtitle" title="${subTitle}">${subTitle}</small>
                </p>
            </div>
        `;
    }

    get isDefined(): boolean {
        /*
        The editorParams propery value is the object that is serialized from the activity editor.
        If the activity is undefined the editorParams propery will evaluate to {}
        */
        return Boolean(this.editorParams.count);
    }
}

DecisionPointItem

The DecisionPointItem has one ingoing path and two outgoing paths. The activity can only be placed under the Yes and No paths of other ConditionItem activities. When you define the parameters of the activity, it becomes a rhombus. The title is displayed in a tooltip when the activity is defined. The decision point does not have title or subtitle elements in its markup.

The supported CSS classes

CSS class Undefined Defined
decision-point

The DecisionPointItem activity implementation

import { DecisionPointItem } from '@sitecore/ma-core';

export class SampleDecisionPointItemActivity extends DecisionPointItem {

    getVisual(): string {
        const subTitle = this.isDefined ? 'Count: ' + this.editorParams.count : '';
        const cssClass = this.isDefined ? '' : 'undefined';

        return `
            <div class="decision-point ${cssClass}" title="Sample decision point item">
                <span class="icon">
                    <img src="/~/icon/OfficeWhite/32x32/gearwheels.png" />
                </span>
            </div>
        `;
    }

    get isDefined(): boolean {
        /*
        The editorParams propery value is the object that is serialized from the activity editor.
        If the activity is undefined the editorParams propery will evaluate to {}
        */
        return Boolean(this.editorParams.count);
    }
}

Implement activity editors

Activity editors are Angular 4 components and therefore what is applicable for Angular 4 components is also applicable for the activity editors.

Note

For more information about what is applicable for Angular 4 components, refer to https://v4.angular.io/docs.

However, activity editors have the following requirements and structure that must be followed:

  • Activity editors must extend the EditorBase class. The EditorBase class has the following members:

    Member Description
    model: any An object that contains the values that define the activity parameters. Each property in the model corresponds to one parameter.
    abstract serialize(): any;

    The serialize method is called when the changes in the editor are committed and applied. It should return an object that contains the values that define the activity parameters where each property in the model corresponds to one parameter.

    Activity editors must provide the implementation of this method.

  • Each activity editor must have its own angular module and you must configure the activity editor as an entry component of the angular module.

  • Activity editors can access the following services that are provided by the Marketing Automation application through dependency injection:

    • Alert Service – shows the success, info, warning, or error alerts.

      To import the service type and the injection token:

      import { ALERT_SERVICE, IAlertService } from '@sitecore/ma-core';
      

      To get an instance of IAlertService:

      constructor(private injector: Injector) {
          super();
          let alertService: IAlertService = injector.get(ALERT_SERVICE);
      }
      
    • Translate Service – provides the access to the dictionary items that are located in the Core database under the /sitecore/client/Applications/MarketingAutomation/Translations item.

      To import the service type and the injection token:

      import { TRANSLATE_SERVICE } from '@sitecore/ma-core';
      import { TranslateService } from '@ngx-translate/core';
      

      To get an instance of TranslateService:

      constructor(private injector: Injector) {
      super();
      
      let translateService: TranslateService = injector.get(TRANSLATE_SERVICE);
      }
      
    • Activity Data Service – provides access to the activity descriptor and classification information. This information is a representation of the activity item that is located in the Master database under the /sitecore/system/Settings/Analytics/Marketing Automation/Activity Types item.

      To import the service type and the injection token:

      import { ACTIVITY_DATA_SERVICE, IActivityDataService } from '@sitecore/ma-core';
      

      To get an instance of IActivityDataService:

      constructor(private injector: Injector) {
      super();
      let activityDataService: IActivityDataService = injector.get(ACTIVITY_DATA_SERVICE);
      }
      
    • Server Connection Service – a wrapper over the Http service that comes with Angular. It provides error handling for failed requests and caching for responses.

      To import the service type and the injection token:

      import { SERVER_CONNECTION_SERVICE, IServerConnectionService } from '@sitecore/ma-core';
      

      To get an instance of IServerConnectionService:

      constructor(private injector: Injector) {
      super();
      let serverConnectionService: IServerConnectionService = injector.get(SERVER_CONNECTION_SERVICE);
      }
      

Note

The services that are provided by the Marketing Automation application can only be accessed through the context injector as shown in the code snippets above. The Marketing Automation application does not come with support for constructor injection for these services.

Sample activity editor implementation

This example shows the implementation of an activity editor for the sample SingleItem activity described in the Implement activities section.

Implementation

The serialize method returns an object that contains one property (count), because the Sample single item activity has one parameter that is the count.

./editor/sample-single-item-editor.component.ts

import { Component, OnInit } from '@angular/core';
import { EditorBase } from '@sitecore/ma-core';

@Component({
    selector: 'sample-single-item-editor',
    template: `
        <section class="content">
            <div class="form-group">
                <div class="row sample-single-item-editor">
                    <label class="col-6 title">Count</label>
                    <div class="col-6">
                        <span class="minus-icon" (click)="decreaseValue()">-</span>
                        <input type="number" class="form-control" [(ngModel)]="count"/>
                        <span class="plus-icon" (click)="increaseValue()">+</span>
                    </div>
                </div>
            </div>
        </section>
    `,
    //CSS Styles are ommitted for brevity
    styles: [``]
})

export class SampleSingleItemEditorComponent extends EditorBase implements OnInit {

    /**
    * Count property is bound to the input element
    */
    count: number;

    /**
    * A component lifecycle hook. Called whenever the model property or any other property in the component changes
    */
    ngOnInit(): void {
        this.count = this.model ? this.model.count || 0 : 0;
    }

    /**
    * Increases the count by 1. Bound to the '+' button
    */
    increaseValue() {
        this.count++;
    }

    /**
    * Decreases the count by 1. Bound to the '-' button
    */
    decreaseValue() {
        this.count--;
    }

    /**
    * Called when the changes in the editor are applied.
    * @returns an object that contains the values of the parameters of the activity, where each property corresponds to a parameter key.
    */
    serialize(): any {
        return {
            count: this.count
        };
    }
}

The FormsModule is imported because the ngModel directive is used in the SampleSingleItemEditorComponent.

./sample-single-item.module.ts

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SampleSingleItemEditorComponent } from './editor/sample-single-item-editor.component';

@NgModule({
    imports: [
        FormsModule
    ],
    declarations: [SampleSingleItemEditorComponent],
    entryComponents: [SampleSingleItemEditorComponent]
})
export class SampleSingleItemModule { }