Overview

Extensions add a specific feature to ITM Platform. They can be used to extend functionality or as a connector between ITM Platform and a third-party system.

You can create your own custom extensions. This guide will help you get started and serve you as a reference when you become an expert.

Each section of this guide is organized into three blocks: Learn by example, Guide, and Reference.

The terminology section will help you with the most commonly used terms.

Basic concepts

Extension script

A extension script is defined in a single JSON file, following this structure:

  1. Extension General Definition
  2. Features and Actions
  3. Configuration
  4. Field Mapping

Learn by example

    {
        "name": "unique-identifier",
        "svgString": "<svg></svg>",
        "details": {},
        "features": [{
                "trigger": "scheduler",
                "actions": [{}, {}]
            }
        ],
        "config": [{}, {}],
        "mapping": []
    }

Examples

All these examples include the code and a tutorial. It is recommended to start with the first and go into more complex examples as you understand the simpler ones.

Extension General Definition

Learn by example

    {
        "name": "unique-name",
        "svgString": "<svg xmlns='http://www.w3.org/2000/svg' fill='#495d73' viewBox='0 0 640 512'><path d='M640 256c0 35.35-21.49 64-48 64c-32.43 0-31.72-32-55.64-32C522.9 288 512 298.9 512 312.4V416c0 17.67-14.33 32-32 32h-103.6C362.9 448 352 437.1 352 423.6C352 399.1 384 400.4 384 368c0-26.51-28.65-48-64-48s-64 21.49-64 48c0 32.43 32 31.72 32 55.64C288 437.1 277.1 448 263.6 448H160c-17.67 0-32-14.33-32-32V312.4C128 298.9 117.1 288 103.6 288C79.95 288 80.4 320 48 320c-26.51 0-47.1-28.65-47.1-64S21.49 191.1 48 191.1c32.43 0 31.72 32 55.64 32C117.1 223.1 128 213.1 128 199.6V95.1C128 78.33 142.3 63.1 160 63.1l103.6 0C277.1 63.1 288 74.9 288 88.36C288 112 256 111.6 256 143.1C256 170.5 284.7 192 320 192s64-21.49 64-48c0-32.43-32-31.72-32-55.64c0-13.45 10.91-24.36 24.36-24.36L480 63.1c17.67 0 32 14.33 32 32v103.6c0 13.45 10.91 24.36 24.36 24.36c23.69 0 23.24-32 55.64-32C618.5 191.1 640 220.7 640 256z'/></svg>",
        "details": {
            "title": {"en": "My Extension","es": "Mi extensión","pt": "Meu extençao"},
            "version": "0.8",
            "shortDescription": {"en": "desc","es": "desc","pt": "desc"},
            "longDescription":  {"en": "long desc","es": "long desc","pt": "long desc"},
            "updated": "2022-05-25",
            "developer": "developer-name"
        }
    }        

Guide

The general definition will provide basic information about the extension you are building. The title, the short description, and the icon will appear on the extension button within the extension panel.

Reference

Features

Learn by example

{"features": [
    {
        "trigger": "event",
        "extension": "providers-demo",
        "entity": "Project",
        "description": "Retrieving providers",
        "event": "updated",
        "actions": []
    }
]}

Guide

The Features section defines the behavior of the extension. It determines what will trigger it, and the actions that will follow.

When a feature is triggered by an event, such as a task update, you must specify the entity(task, in our example) and the event (update, in the same example).

When the feature is triggered by the scheduler, you must create the synchronization frequency configuration option, so the user can set the value.

Reference

(*) Denotes a mandatory field

Checkpoints

Actions

Learn by example

     {
        "action": "restcall",
        "url": "https://api.itmplatform.com/v2/mycompany/projects/{{ input.project.Id }}",
        "method": "GET",
        "token": "{{ logininfo.Token }}",
        "description": "Retrieve project",
        "output": "project",
        "payload": "",
        "headers": "{\"token\" : \"{{ logininfo.Token }}\", \"myheader\" : \"header value\"}"
        "dataType": "application/json"
    }

Guide

Actions are pieces of code executed when a feature is triggered. Actions are typically chained to one another by sending the output value from the parent action to the child action.

Actions admit loops and conditionals.

Reference

Checkpoints

When saving an extension, the following validations will run. If the script doesn't pass them, you will be able to save it but not activate the extension.

Action Types

The action value can be one of: restcall, soapcall, validate, map, email, or loop

"action": "restcall"

Performs a REST call to any third-party API or ITM Platform's.

Learn by example

    {
        "action": "restcall",
        "url": "https://jsonplaceholder.typicode.com/todos/",
        "method": "GET",
        "description": "Retrieves a to-do list",
        "output": "todos",
        "payload": "",
        "dataType": "application/json"
    }

Guide

A REST call is the most common way to get or post data in any API, including ITM Platform's. Use it as you would with any tool, such as Postman, curl, or your favorite programming language.

Reference

"action": "soapcall"

Performs a SOAP call to any third-party system.

Learn by example

{
    "action": "soapcall",
    "url": "http://testurl.com/testservice",
    "soapaction": "testmethod",
    "payload": "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tem=\"http://tempuri.org/\"><soapenv:Header/><soapenv:Body><tem:CallPost><tem:name>{{ input.SomeInt }}</tem:name></tem:CallPost></soapenv:Body></soapenv:Envelope>",
    "dataType":  "application/json",
    "authentication": "basic",
    "user": "testuser",
    "pass": "testpass",
    "output": "jsonoutput"
}

Guide

A SOAP action will require a url, a soapaction, a payload and an output. The dataType attribute must be "application/json". It can also include an authentication attribute, along with user and pass.

Data Types

The following example parses the output as xml, which can be used later on to retrieve values with mapping

{
    "action": "soapcall",
    "url": "http://testurl.com/testservice",
    "soapaction": "testmethod",
    "payload": "{{ output1 }}",
    "dataType":  "application/xml",
    "output": "dataoutput"
}
{
    "action": "soapcall",
    "url": "http://testurl.com/testservice",
    "soapaction": "testmethod",
    "payload": "{{ output1 }}",
    "dataType":  "application/json",
    "output": "dataoutput"
}

Reference

action": "map"

Learn by example

    {
        "action": "map",
        "template": "{ \"name\": \"{{ input.name }}\", \"value\": \"{{ input.value }}\" }",
        "dataType": "application/json",
        "output": "outputVar"
    }

Guide

The map actions allow mapping values from existing variables. In the example above, the variables name and value coming from input will be transformed to JSON.

Reference

"action": "validate"

Used in synchronous features, it performs a validation before proceeding.

Learn by example

    {
        "action": "validate",
        "validateCondition": "Convert.ToInt32(input.Status.Id) == 10 && Convert.ToString(input.Description) == \"\"",
        "message": "When using the status {{ input.Status.Description }} you need to provide a description"
    }

Guide

The validate action will require a validationCondition, and a message that will be displayed to the final user if the condition is not met. It is generally used to perform custom validations that may need to change the way ITM platform works. For example, if you need to validate that a particular field has a value depending on other fields.

Reference

"action": "email"

Sends an email to the given address/es

Learn by example

        {
          "action": "email",
          "to": "yourname@yourcompany.com",
          "subject": "This subject admits templates",
          "body": "<html><h1>This email body admits templates too!</h1></html>"
        }

Guide

Sends an email to the specified recipient(s). The sender will be notifier@itmplatorm.com, an address that doesn't admit replies.

Reference

"action": "loop"

The loopaction will iterate over elements coming from previous calls.

There are different types of loops: simple, with variables, an with variables and conditions.

Simple loops

Learn by example

 { 
    "trigger": "event",
    "event": "updated",
    "entity": "Project",
    "actions": [
        {
            "action": "loop",
            "loop": {
                "var": "myarray",
                "output": "item",
                "index": "index"
            },
            "actions": [
                { 
                    "action": "restcall",
                    "url": "http://your-company/{{ item.name }}/{{ index }}",
                    "method": "post",
                }
            ]
        }
    ]
} 

Guide

A loop action will parse an object coming from a previous action and run an actions array over the outcome.

Reference

Loops with variables

We can also loop with variables inside an action. For example:

Learn by example

{
    "trigger": "event",
    "event": "updated",
    "entity": "Project",
    "action": "loop",
    "loop": {
        "var": "myarray.values",
        "output": "singleElement",
    },
    "actions": [
        {
            "action": "restcall",
            "url": "http://testurl.com/testapi",
            "method": "POST",
            "payload": "{\"Columns\": \"Id,Name,JiraId,Type.Id,MethodTypeId\",\"filter\": { \"JiraId\" : {{ singleElement.id }} } }",
            "output": "outputVar",
            "dataType": "application/json"
        },

Guide

Reference

Loops with variables and conditions

We can also loop with variables and conditions inside an action. For an example:

{
    "trigger": "event",
    "event": "updated",
    "entity": "Project",
    "action": "loop",
    "loop": {
        "var": "myarray.values",
        "output": "singleElement",
        "condition": "somevariable != null"
    },
    "actions": [
        {
            "action": "restcall",
            "url": "http://testurl.com/testapi",
            "method": "POST",
            "payload": "{\"Columns\": \"Id,Name,JiraId,Type.Id,MethodTypeId\",\"filter\": { \"JiraId\" : {{ singleElement.id }} } }",
            "output": "outputVar",
            "dataType": "application/json"
        },
        {
            "action": "restcall",
            "url": "http://testurl.com/testapi",
            "condition": "Convert.ToInt32(outputVar.total) > 0",
            "method": "PATCH",
            "payload": "{\"Name\": \"{{ singleElement.name }}\",\"Description\": \"{{ singleElement.description }}\" }",
            "output": "updatedoutputVar",
            "dataType": "application/json"
        }
    ]
}

Guide

Reference

Action conditionals

Conditionals

Learn by example

    {
        "action": "restcall",
        "url": "https://api.hubapi.com/companies/v2/companies/recent/modified?hapikey={{config.apikeyHubspot }}&since={{ context.LastExecution }}", 
        "condition": "{{ context.LastExecution }} != null",
        "method": "GET",
        "description": "retrieve all clients filter lastExecutiondate",
        "output": "companiesHS",
        "payload": "",
        "dataType": "application/json"
    }

Guide You can put a condition to the event to check whether the actions should be executed.

{
"condition": "{{input.SomeObject.Array.0.Array.1.SomeData}} == 3"
}

Extension Configuration

The "config" array will determine the content rendered in the extension's configuration tab on ITM Platform, displaying all fields required for your extension to operate.

Learn by example

  {
    "config": [

            {
            "name": "option-name",
            "label": {
                "en": "Label in English",
                "es": "Etiqueta en español",
                "pt": "Rótulo em inglês"
            },
            "tooltip": {
                "en": "A brief description for the tooltip",
                "es": "Una breve descripción para el tooltip",
                "pt": "Uma breve descrição para o tooltip"
            },
            "type": "date",
            "required": true
        }
    ]
}
}

Guide

These fields are represented by the configuration options within the config array.

Once the user fills out the ITM Platform's "Configuration" tab, the values will be available through the config object, available to be used in features and actions.

These are some examples of the usage of the config object throughout a script:

"url": "{{ config.url }}/rest/api/3/project/search?maxResults=50&startAt=0"
"username": "{{ config.user }}"
"payload": "{\"Name\": \"{{ singlejiraproject.name }}\",\"Description\": \"{{ singlejiraproject.description }}\",\"InternalCode\": \"{{ singlejiraproject.key }}\",\"JiraURL\": \"{{ config.url }}\",\"SynchronizationSource\": \"Jira\"{{#ifempty singlejiraproject.projectCategory }}{{else}},\"TypeId\": \"{{ map config.mapping.projecttype.external singlejiraproject.projectCategory.id }}\"{{/ifempty}} }"

These configuration options can define any custom value that you need to store (eg. the API key required to log in), but it also interprets a few hardcoded values.

The identifier for each configuration option is the name property

Reference

The header type will render regular section header Section headers on ITM Platform's configuration tab, adding a adding a visual separation between fields.

    {
        "config": [
        {
                    "name": "header-credentials",
                    "label": {
                        "en": "Credentials section",
                        "es": "Sección de credenciales",
                        "pt": "Seção de credenciais"
                    },
                    "type": "header"
                }
        ]
    }

Hardcoded configuration options

The hardcoded configuration options will be identified by their name and define a specific behavior in the extension interpreter. These hardcoded options are:

Activate the extension ("name": "isactive")

Provides the option for the user to activate the extension within the "Configuration" tab.

Learn by example

{
    "config": [
            {
                "name": "isactive",
                "label": {
                    "en": "Activate connector",
                    "es": "Activar conector",
                    "pt": "Ativar o conector"
                },
                "tooltip": {
                    "en": "When selected, the connector will be active and will start synchronizing data",
                    "es": "Cuando esté seleccionado, el conector estará activo y comenzará a sincronizar datos",
                    "pt": "Quando selecionado, o conector estará ativo e começará a sincronizar dados"
                },
                "type": "checkbox",
            }
    ]
}

Guide This will display a checkbox to activate the extension on the configuration tab in ITM Platform.

⚠️ If you don't add this configuration, you will not be able to activate the extension later on.

Reference

Properties with special considerations:

Synchronization frequency ("name": "synchronizationfrequency")

Provides the configuration option for the user to set the frequency at which the extension will be triggered. It is required when the feature is triggered by scheduler

Learn by example

{
    "config": [
            {
                "name": "synchronizationfrequency",
                "label": {
                    "en": "Synchronization frequency (minutes)"
                },
                "tooltip": {
                    "en": "Frequency you wish to establish for the synchronization of the data from your account"
                },
                "type": "string",
                "required": true
            }
    ]
}

Guide It will display in the "Configuration" tab a user input to introduce the synchronization frequency in minutes for the scheduler.

The minimum frequency is 60 minutes. We recommend setting the frequency at the maximum your business case accepts.

It will apply to the feature(s) that have the scheduler trigger.

Reference

Properties with special considerations:

Field Mapping

The mapping configuration allows you to establish an equivalence between two systems, typically ITM Platform and a third party.

This is useful to create synchronizations based on the values of one or more fields.

As an extension developer, you don't set those relationships. You give the user the means to create relationships in the configuration tab in ITM Platform.

Learn by example

{
 "mapping": [
        {
            "name": "projecttype",
            "label": {
                "en": "Project Type",
                "es": "Tipo de proyecto",
                "pt": "Tipo de Projeto"
            },
            "external": { 
                "method": "GET",
                "url": "@@url@@/rest/api/3/project/type",
                "authentication": "Basic",
                "user": "@@user@@",
                "pass": "@@pass@@",
                "id": "key",
                "name": "formattedKey",
                "columnName": {
                    "en": "Jira",
                    "es": "Jira",
                    "pt": "Jira"
                }
            },
            "internal": {
                "method": "GET",
                "url": "v2/@@companyId@@/GetProjectTypes",
                "id": "Id",
                "name": "Name",
                "columnName": {
                    "en": "ITM Platform",
                    "es": "ITM Platform",
                    "pt": "ITM Platform"
                }
            }
        }
 ]
}

Guide

In the example above, the configuration panel will show two columns filled with the project types of ITM Platform and another with the project types of Jira. This will allow the user to establish that --for example- the project type "Software" in ITM Platform is equivalent to the project "Development" in Jira.

Reference

Event Reference

The following are the events that ITM Platform triggers.

Trigger Entity Action When Input
scheduler This executes from scheduler context.LastExecution
event Task inserted When a task is inserted { { "accountId", accountId }, { "projectId", projectId }, { "userId", userId }, { "task": { "Id", taskId }, { "Name", taskName }, { "JiraTaskId", JiraTaskId }, { "KindId", taskKindId }}}
event Task updated When a task is updated { { "accountId", accountId }, { "projectId", projectId }, { "userId", userId }, { "task": { "Id", taskId }, { "Name", taskName }, { "JiraTaskId", JiraTaskId }, { "KindId", taskKindId }}}
event Project inserted When a project is created { { "accountId", accountId }, { "userId", userId }, { "project": { "Id", projectId }, { "Name", projectName }, { "TypeId", typeId }, { "Description", description }}}
event Project updated When a project is updated { { "accountId", accountId }, { "userId", userId }, { "project": { "Id", projectId }, { "Name", projectName }, { "TypeId", typeId }, { "Description", description }}}
event Purchase updated When a Purchase is update { { "accountId", accountId }, { "projectId", projectId }, { "userId", userId }, { "purchase", new JObject() { { "Id", purchaseId }, { "Name", purchaseName }, { "ActualAmount", actualAmount }, { "ProjectedAmount", projectedAmount } } } }
event Revenue pre insert When a Revenue is going to be created { { "ProjectedAmount", projectedAmount }, { "ActualAmount", actualAmount }, { "ProjectId", projectId } }
event Revenue inserted When a Revenue is created { { "Id", revenueId }, { "Name", revenueName }, { "DueDate", dueDate }, { "ProjectedAmount", projectedAmount }, { "ActualAmount", actualAmount }, { "Status", statusId }, { "ProjectId", projectId }, { "UserId", UserId }, { "AccountId", AccountId } }
event Revenue pre update When a Revenue is going to be updated { { "ProjectedAmount", projectedAmount }, { "ActualAmount", actualAmount }, { "ProjectId", projectId } }
event Revenue updated When a Revenue is updated { { "Id", revenueId }, { "Name", revenueName }, { "DueDate", dueDate }, { "ProjectedAmount", projectedAmount }, { "ActualAmount", actualAmount }, { "Status", statusId }, { "ProjectId", projectId }, { "UserId", UserId }, { "AccountId", AccountId } }

Event bubbling up

Events don't bubble up how you may be used in other environments. But if an entity event affects others, such as its parents, these parents will also trigger an event.

For example, you change the end date of a task. That will trigger the event updated on the entity Task. Now, consider these two scenarios:

Template Syntax

Wherever templates are supported, you can use curly braces to wrap an expression {{ expression }} to generate the text that you need.

The template system is based on Handlebars and supports all the native expressions, plus a set of additional expressions that will come in handy when building an extension.

caveat: If you need to include quotes within the template (tipically in the payload), you need to escape internal quotes with a backlash. {{ "payload": "{\"Value\": 23 }}

Additional expressions

The following are Handlebars expressions created to specifically interact with the extension interpreter.

Data type converters

Because the extension script is written in plain text, you will need to tell the interpreter what data type you intend to pass.

Value replacements

Conditionals

-->

Accessing array of items

Mustache syntax won't work. To access the first item in a array, instead of something like taskTeam.list.[0].Team, it will have to be: taskTeam.list.First.Team

Type modifiers

To compare or operate with values, you need to specify the type of value. Learn by example

{
"condition": "Convert.ToInt32(taskdetails.total) > 0 && Convert.ToInt32(taskdetails.list.0.KindId) == 3"
}

Guide

To perform logical operations, you need to specify the type of value. The script interpreter will throw an error otherwise:

There are the data converions you can use

Script Variables

Learn by example

{
    "url": "@@ITMAPI@@/my-company/projects/",
}

Guide

The extension interpreter provides you with variables that you can use. In the example above, we used @@ITMAPI@@ to call ITM Platform's login endpoint. The outcome of this case would be: https://api.itmplatform.com/my-company/projects

Reference

URL

Let's take the URL as an example.

Learn by example

{
"url": "@@ITMAPI@@/@@AccountName@@/login/{{ config.apikey }}",
}

Guide

In the example above, we are using @@variables@@ and {{ template syntax }} to call ITM Platform's login endpoint. The outcome in this case will be this: https://api.itmplatform.com/my-company/login/86d4bb2a-2129-40f5-8311-918c6da16823

Debugging

To debug an extension, you need to look in the logs to know what happened during its execution. Because the script gets interpreted in ITM Platform's servers, there is no "run and debug" action as you may be used to in other languages.

When an extension is triggered, either by the scheduler or an event, two different types of logs are fed.

Deploying an Extension

To create and deploy a script, you must have the proper license and permissions within ITM Platform.

  1. From the Extension Panel, click on New Extension
  2. Fill out the name and other metadata. The extension name will be used internally as the unique identifier and cannot be changed. For example, 'my-extension'. It is case insensitive. No special characters or spaces are allowed
  3. Create the script in the "Code Editor" section
  4. Activate the extension from the Configuration section

Multi-language

Terminology

Examples

Obtaining the ITM Platform authentication token

{"actions":[{
    "action": "restcall",
    "url": "https://api.itmplatform.com/myCompany/login/{{ config.apikey }}",
    "method": "POST",
    "description": "Login with apikey",
    "output": "logininfo",
    "payload": "",
    "dataType": "application/json"
},
{
    "action": "restcall",
    "url": "https://api.itmplatform.com/myCompany/projects/22333",
    "method": "GET",
    "token": "{{ logininfo.Token }}",
    "description": "Retrieve project",
    "output": "project",
    "payload": "",
    "dataType": "application/json"
}]}

Result object in the logininfo variable (output of the first action):

{
    "Token": "5955520211116162058986",
    "UserID": "33989",
    "AccountID": "18137",
    "Result": "1",
    "ResultStatus": "Success"
}