JavaScript for Automation Rules

JavaScript blocks in Automation Rules provide the greater amount of flexibility which can't always be achieved with configuration through graphical user interface or DSL.

For example, the rule may need to make several queries to some API, Targetprocess' or your internal company's, make some algorithmic choices or calculations.

Currently, Automation Rules support JavaScript in filter and action blocks. They are mostly similar in terms of capabilities, with the primary difference in the expected return values from scripts.

Filters

A script in filter block should return true or false to control whether the following actions should be executed.

Here's an example script which checks that the name of the modified User Story matches some regular pattern:

// `args` object contains the information about event which triggered the rule.
// In our case it is a modification of Resource, i.e. User Story
const userStoryName = args.Current.Name;

// Return `true` if the name contains something like "AwesomeStory14"
return userStoryName.match(/AwesomeStory#[0-9]+/i) !== null;

Scripts also have access to a powerful Targetprocess querying API! Let's take a look at another example of filter script. This time it will check that all Inbound Relations of User Story (added as "Link" relations) are in final state:

// Once again, `args` contains info about event which triggered the rule.
// For rules reacting to modifications of Targetprocess entities,
// `args.Current` contains the snapshot of the modified entity.
const userStoryId = args.ResourceId;
// `context.getService` function lets your script get access to various services and APIs
// For example `targetprocess/api/v2` service lets the script make queries to Targetprocess API v2
const api = context.getService("targetprocess/api/v2");

// `querySpec` here mirrors the shape of API v2 query parameters,
// such as `select`, `where`, `result`, etc.
const querySpec = {
    // Find all relations of type Link NOT in Final state which are linked to this User Story
    where: "relationType.name == \"link\" and entityState.isFinal == false and outboundGeneral.id == " + userStoryId,
    // And get back the total number of such relations
    result: "count"
}

// Query tpondemand.com/api/v2/inboundAssignables to get the number of relations.
// Note that it is an asynchronous operation, so you must use `await` keyword here.
const count = await api.queryAsync("inboundAssignables", querySpec);

// Continue the rule execution only if there are no relations in not final state.
return count === 0;

Actions

Action blocks allow the script to say something like "Update this User Story" or "Create a new Bug" or "Send HTTP request to this URL". Action block must return an array of so-called "commands", describing what should the rule do.

Each object in returned array must include 2 fields:

  • command, which is a name of the command to execute. For example, targetprocess:CreateResource or targetprocess:UpdateResource or targetprocess:MoveToState
  • payload, which contains the parameters of that command.

Here's an example script which always tell to create a new User Story with name "Test User Story" in project #10:

return [
  {
    command: "targetprocess:CreateResource",
    payload: {
      resourceType: "UserStory",
      fields: {
        Name: "Test User Story",
        Project: {
          Id: 10
        }
      }
    }
  }
];

Let's take a look at more sophisticated script, which moves all open Inbound Assignables of User Story (connected via "Link" relation) to final state:

const userStoryId = args.ResourceId;
const api = context.getService("targetprocess/api/v2");
const querySpec = {
    select: "{id, entityType: entityType.name}",
    where: "relationType.name == \"link\" and entityState.isFinal == false and outboundGeneral.id == " + userStoryId
};

// Retrieve an array of matching inbound assignables.
const openInboundAssignables = await api.queryAsync("inboundAssignables", querySpec);

// Build a result array, creating a command for each assignable.
const commands = openInboundAssignables.map(related => {
  return {
    command: "targetprocess:MoveToState",
    payload: {
      resourceId: related.id,
      resourceType: related.entityType,
      stateKind: "Final"
    }
  }
});

return commands;

An example of creating a certain number of tasks for newly created User Story based on it's name, something like a template:

const commands = [];

if (args.Modification === "Created") {
  const userStoryId = args.ResourceId;
  const userStoryName = args.Current.Name;
  
  // For example, if the name of User Story contains "Prefill with 14 tasks",
  // this block will produce 14 commands to create a Task
  const match = userStoryName.match(/\[Prefill with ([0-9]+) tasks\]/i);

  // If regular expression is matched, ...
  if (match) {
    // ... then the number of tasks will be stored in the first capturing group,
    // which is available as match[1].
    const taskCount = parseInt(match[1]);
    
    for (let i = 0; i < taskCount; i++) {
      commands.push({
        command: "targetprocess:CreateResource",
        payload: {
          resourceType: "Task",
          fields: {
            Name: "Task#" + (i + 1).toString(),
            UserStory: { Id: userStoryId }
          }
        }
      });
    }
  }
}

return commands;

An example of creating Bug as a relation for newly created Request:

const commands = [];
const requestId = args.ResourceId;
const requestName = args.Current.Name;
const projectId = args.Current.Project.Id

commands.push({
    command: "targetprocess:CreateResource",
    payload: {
            resourceType: "Bug",
            fields: {
                Name: "Auto related bug for request " + requestName,
                Project: {id: projectId},
                MasterRelations: {
                    items: [{
                        RelationType: {id: 1},
                        Master: {id: requestId}
                    }]
                }
            }
        }
});

return commands;