Friday 28 July 2017

Basic Introduction to the TACTIC Workflow Engine

The Workflow Engine is one of the key technology components of TACTIC.  A workflow defines a set of connected process nodes.  Each node represents some work that needs to be done within the entire workflow which constitutes a job.

The workflow engine drives the entire work process forward.  It is responsible
for sending messages so that upstream and downstream process are notified of
important events.  For example, if a downstream process sets a task to complete, then all the next upstream processes will set the task to pending.

In TACTIC, any sobject can have a pipeline defined.  Fundamentally,t his means that any sobject can go through a series of processes, have tasks assigned to it and have files checked into it.

Pipeline or Workflow?

Internally, TACTIC uses word pipeline, however, this word is not commonly
recognized outside of the Visual Effects / Computer Graphics indusry.  Workflow is commonly used in every other industry, so this is the terminology that we will use from now on.

Process Node Types

By default, all nodes are manual nodes.  These nodes represent processes
where manual work must be done.  In general, a task is created which is
associated with this node.  This task will have someone assigned to it
and can have a specific start and end date.

When a task that belongs to a process changes its status, a message is
sent to that processes.  This allows the workflow engine to react to this
change in status.  For example, if a "complete" message is sent from the
task to the process, then the process "send" a pending message to all of
its output connections.  This output notification is automatic.

If the output node is another manual node, then the process will notifiy any
tasks associated with it and set them to pending.  This task would now
show up on a "pending" task list for users (who would set it to "In Progress" when working on the task)

If the output node is another node type, the behavior would be quite different.
For example, if the output node was an "action" type, then the script code
associated with that process would automatically get executed.

Action nodes are non-blocking.  They will automatically execute their programmed task and without any manual involvement from a user, when they complete, they will  send a "pending" message to the next process.

Approval node is a specific type of node that is very similar to a
manual node.  There is always an assigned task that is associated with
it however, usually, the task status options are more limited.  Also,

many widgets recognize approval nodes and adjust accordingly.

Connecting Processes

These nodes can be connected together in any combination.

manual1 -> auto1 -> auto2 -> manual2

In this example, after manual1 is set to "complete", "auto1" would automatically
execute.  On completion of "auto2" will automatically execute.  Finally,
when "auto2" completes, "manual2" will receive a "pending" signal which will
set the task in this node to a status of "pending".

The nodes can also be connected in parallel:

manual1 -> auto1 -> manual2
        -> auto2 ->

In this case, both auto1 and auto2 would be executed simultaneously.  Manual2
would not receive the pending signal until both "auto1" and "auto2" are complete.

As can be seen from these examples, the workflow engine pushes process progress
along the various steps defined in the definition of the workflow.  Users
do not need to remember to execute certain scripts in certain ways in certain
order.  This can all be hidden from the user and be executed exactly as they
were designed to do.

There are a number of other nodes in built into TACTIC which enhance how the
workflow engine behaves.  It is even possible to write custom nodes types
entirely in Python.  These nodes can be loaded in as a TACTIC plugin
and used just like a native node.

Tuesday 4 April 2017


TACTIC 4.6 just introduced a new generic REST API which implements most of the TACTIC API.  This opens up the TACTIC API to almost all programming languages.   There are still some advantages to using the client side libraries for Python and Javascript as there is some custom convenience and logic written in the client side libraries.  However, for many important operations, the rest API is sufficient.  This is especially useful when integrating TACTIC with such platforms as WordPress or Drupal or AngularJS.

Since the TACTIC API functionality is very deep, the first REST implementation has been to only support POST requests.   This provides the access to all of the arguments of the API methods without the inconvenient limitation of using a URL to provide all of the arguments to methods.

The following example will made use of the PHP curl functionality wrapped up in a convenience function.  It is assumed that you already have a login ticket.  A login ticket is any non-expired entry in the "ticket" table in the "sthpw" database.

The following function wraps up all the settings needed to access a TACTIC server:

function executeREST($data) {
    $server = "";
    $login_ticket = "8f096a8359bbc355943f93545684b90e";
    $project = "my_projects";
    $url = $server."/".$project."/rest";
    $data['login_ticket'] = $login_ticket;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_FAILONERROR, true);

    $result = curl_exec($ch);
    if (curl_error($ch)) {
        throw new Exception(curl_error($ch));
    $result = json_decode( $result );
    return $result;

With this function, we can access the api simply with a single line.  We can start with a simple ping.

$data = [
    'method' => 'ping'
echo executeREST($data);

This should print "OK", which means that the code has contacted the TACTIC server and was authenticated.  The method

Next, we can try a simple sobject with a search key:

$data = [
    'method' => 'get_by_search_key',
    'search_key' => $search_key,
print executeREST($data);

And we can also use expressions:

$data = [
    'method' => 'eval',
    'kwargs' => json_encode( [
        'expression' => '@SOBJECT(workflow/asset)',
        'single' => true,
    ] ),
$result = executeREST($data);

Note the difference in the arguments between this method (eval) and the last (get_by_search_key).  There are two ways for arguments to be passed through and both are valid for either function.  You can use the arguments explicitly or you can wrap the arguments in a kwargs key.  However, the kwargs key is assumed to be JSON encoded and allows for complex data structures to be passed through.  The post interface does not allow for complex data structures, so these should be JSON encoded.

This opens up the TACTIC API to many languages, in particular PHP and Ruby which are used in server side processing of popular web platforms.  All of these platforms have a very weak ability to handle complex data and really have no asset management capabilities.  This API makes it easy to integrate a powerful back-end with these content delivery systems.

Friday 31 March 2017

TACTIC Javascript API Start-up

Using the TACTIC 4.6 API from javascript is very simple.  You just have to reference
a single javascript file.  You can either reference a local copy of this file:

<script src="js/tactic.js"> </script>

Or you can reference it from TACTIC server itself:

<script src="http://<server>/context/spt_js/tactic.js"> </script>

Replace <server> with your own server IP or domain name running TACTIC.  This
will import all the necessary javascript libraries and functions to interact with
the TACTIC server.

Next you will need to generate an API ticket.  This key is a long unique alpha-numeric string of characters.  This ticket is added to the API key, which has the form:


So, for example, you would set up the server with the following:

tactic = TACTIC.get();
var key = http://<server>/workflow/workflow/ticket/571be6bb09142c4c6b883e0c9f310f57");

If you wish to build tickets dynamically, then this can be done from a custom login
screen that captures a login and password.  For there, you would execute the following:

tactic = TACTIC.get();
var ticket = tactic.get_ticket(name, password);

where "server" is the IP or domain name of your TACTIC Server and name and password
are user entered fields.  Once you have a ticket, this can be used until the ticket
expires on the server.

You are all ready to access any of TACTIC API functionality.  To check if you can access the server, simple run the ping method:

alert( )

If it returns "OK", you have successfully accessed the TACTIC server with proper credentials.

At this point, you have the full API at your disposal.  As an example, you could run a query for tasks completed assigned to Fred:

tasks = tactic.query("sthpw/task", {
    filter: [['status','Complete'],['assigned','fred']],
} );

If you wish to run this asynchronously, you would add an on_complete callback:

tactic.query("sthpw/task", {
    filter: [['status','Complete'],['assigned','fred']],
    on_complete: (tasks) => {
} );

Or if you wish to use promises:

tactic.p_query("sthpw/task", {
    filter: [['status','Complete'],['assigned','fred']],
} )
.then( (tasks) => {
} );

Or we could use an expression to get the same result:

var expression = "@SOBJECT(sthpw/task['status','Complete']['assigned','fred'])"
.then( (tasks) => {
} );

The javascript implementation is a fully featured TACTIC API.  This is useful for
many applications such as server side javascript such as NodeJS, Mobile apps that
run on javascript.  It can also be used in WordPress or Drupal applications on the

Thursday 16 March 2017

Executing server side scripts from browser

TACTIC provides a number of mechanism to Python code on the server. We can start with a widget in the Custom Layout Editor with the HTML definition:

 <input type=”button press_me” name=”Press Me”/>

In the behaviors:

<behavior class=”press_me”>
 var script_path = “my_scripts/press_me”;
 var kwargs = {};
 var server = TacticServerStub.get();
 server.execute_python_script(path, kwargs);

In the Script Editor, you can create a script with the folder “my_scripts” and title “press_me”.

print “Button is pressed”

This simple example prints “Button is pressed” to the console.  Of course, this script can contain any Python code.  In this example, the script is blocking.  This is not usually desirable because blocking scripts cause the Javascript engine to wait until the full execution of the script.  Except for very fast and small scripts, this will negatively influence the user experience.  In order to run the script asynchronously, you could run the following call instead:

server.execute_python_script(path, kwargs, {
   on_complete: function() {
       spt.alert(“Script Complete”);
} )

This works well for simple scripts that need to be executed on the server.  It is often desirable to use full python classes to execute scripts on the server side.  To execute a class on the server, you need to derive the class from the TACTIC Command class and override the execute function.

from pyasm.command import Command

class MyCmd(Command):
   def exectute(self):
       print “Running ....”
       print “kwargs: “, self.kwargs = {
         test: 456

Instead of “server.execute_python_script” in the behavior, you would have:

var cmd = “foo.MyCmd”
var kwargs = {
   test: 123
var server = TacticServerStub.get()
var ret_val = server.execute_cmd(cmd, kwargs);

The kwargs for the command will be the dictionary with key “test” and value “123”.  The returned value is a dictionary that will contain any error information as well as an “info” dictionary that will contained any returned data from the command itself.  In the example above, it would return the dictionary with the key “test” and value “456”.

This method allows you to create interface elements and then run custom scripts on the server based on interaction with those interface elements.  The HTML code is connected to the behaviors which run Javascript code.  The javascript uses the TACTIC API to execute commands directly on the TACTIC Server and provides a clear mechanism to send information to the command as well as receive information from the command.