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.

Friday 7 October 2016

Introduction to TACTIC Plugins

TACTIC Plugins are a way of packaging up functionality and distributing them
to other projects or other servers.

The Plugin architecture is very flexible and almost any functionality, large
or small can be packaged in as a plugin.

A TACTIC Plugin can contain:

  1. data
  2. configuration
  3. python class
  4. 4mages and media
  5. css and javascript libraries

At the core of a TACTIC Plugin is a manifest file.  The manifest file tells TACTIC how to load and unload a plugin.  It is an xml file and is placed at the root of the plugin folder.  A simple example of on is as follows:

  <sobject search_type="custom/asset"/>

It declares what data belongs to the plugin.

The above manifest will create a plugin that will export all entries from the "custom/asset" search
type to a file called "custom_asset.spt".  Files with ".spt" extension are TACTIC custom files that are used for importing and exporting data.  The syntax used is exactly the same as Python (a python processor is used to read the data), however they are not self-contained and executable and thus not really considered ".py" files. However, if desired, any python code could be added and it should be processed correctly.

When loading the plugin, the "sobject" will read the corresponding ".spt" file and insert the new sobjects into the database.  On unloading the plugin, the entire table will be removed.  This because the line states that the entire table belongs to the plugin.  This is useful for custom search types, but often it is desireble for the plugin to own a subset of table.  The easiest way to do this is with an expression

<sobject search_type="custom/asset" expression="@SOBJECT(custom/asset['asset_type','image'])"/>

This will export all the sobjects with the column asset_type equal to "image".  This
allows multiple plugins to share data in the same search type.  Care must be taken so
that a given sobject only belongs to a single plugin to avoid conflicts.

To control the output file the data is written to, the "path" attribute can be used.

<sobject search_type="custom/asset" path="config/asset.spt"/>

This file created will be relative to the manifest file.  This can be used to
break up a single table into multiple files, if that is desired.

It is quite common not to wnat all the columns in the exported file.  The
"ignore_columns" attribute can be used to specify columns that are not exported.

<sobject search_type="custom/asset" igonre_columns="id,code,timestamp"/>

The code and id are commonly not exported.   The "id" column is usually auto
generated, so their exact values are not important as long as they are not used
to relate to other table.  This prevents conflicts of ids loaded in from other
plugins.  If the code needs to be exported, it is important to ensure that this
code will be unique enough to be loaded even if data is already filled in that table.

If the definition of a search_type needs to be exported, the "search_type" tag can
be used:

<search_type code="custom/asset"/>

This will save out the entire search_type definition into the ".spt" file.  This makes
it possible for a plugin to own the definition of a search_type allowing a plugin
to define a data model.

With the combination of the <search_type> tag and the <sobject> tag, almost any
configuration data can be stored in a plugin.

When a plugin is loaded, the plugins folder is also accessible from the web server.
This means that images and other files can be put into a plugin and be visible by
a client web broser.

For example:

<img src="/plugins/test_plugin/media/image.png"/>

This would be visible by the following URL:


This allows the plugin to package up different image files as well as javascript and css files.

This was just a small introduction to TACTIC plugins.  The plugin architecture is flexible enough that almost any TACTIC functionality can be encapsulated in one.

Friday 30 September 2016

Dynamic updates when data changes.

Dynamic updates allow any part of the page to update itself based on change occurring  on the server.

To illustrate, begin with a simple element that display the number of assets in the system.


In Python, the asset count would be retrieved as follows.

asset_count = server.eval(“@COUNT(example/asset)”)

When the view loads, it would process the asset count on the server side and then fill in the value in HTML.  The problem comes when someone else who is using the system adds another asset after a view has been loaded.  The data displayed is now out of date.  Normally the entire page, or in TACTIC’s case, the widget needs to be reloaded to update the correct value. However, knowing when data has changed presents a challenge.

Dynamic update solves this problem by providing a mechanism to update some part of the content based on some specified updated criteria.

For example, in the widget above, we will add an update through javascript:

<div class=”test_update”>${asset_count)</div>

In python, we would write:

kwargs[‘expr’] = “@COUNT(example/asset)”
asset_count = server.eval(kwargs.get(“expr”))

And in behaviors, we would add:

<behavior class=”test_update” event=”load”>
 var expr = kwargs.expr;
 spt.update.add( bvr.src_el, {
     expression: expr
 } );

This will check the expression periodically to see if the value has changed on the server.  It the value changes, such as a new asset was added, this would be detected and the value of the expression will replace what is inside the “test_update” element.  This is very useful for keeping values up to date on a page without having to refresh.

There is another argument called interval.  Normally, a dynamic update will check every couple of seconds, however, most data this is much too often.  It is possible to slow down the number of checks by setting the interval.

<behavior class=”test_update” event=”load”>
 var expr = kwargs.expr;
 spt.update.add( bvr.src_el, {
     expression: expr,
     Interval: 10,
 } );

This will do a check on the server about every 50 seconds.

If a widget is dependent on the values of a single SObject, then we can add search_key parameter instead and this will detect if any change to a particular SObject has been made.

<behavior class=”test_update” event=”load”>
 var search_key = bvr.kwargs.search_key;
 spt.update.add( bvr.src_el, {
     search_key: search_key,
     expression: “@GET(.status),
 } );

With the addition of a search_key, TACTIC will check if any changes have been made to that SObject.  If so, it will replace the “test_update” widget with the value returned from the corresponding expression.

In order to have more control, it is possible to run some javascript.

<behavior class=”test_update” event=”load”>
 var search_key = bvr.kwargs.search_key;
 spt.update.add( bvr.src_el, {
     search_key: search_key,
     cbjs_action: “spt.panel.refresh(bvr.src_el)”
 } );

This will update refresh the next parent refreshable widget when the corresponding SObject changes.  With a javascript callback, it is possible to do a wide variety of actions.

Finally, it is possible to attach a dynamic behavior purely in Python.

div = DivWdg()
div.add_update( {
   “expression”: expr
} )

This allows a widget that is entirely written using a Python class to make use of dynamic updates.

Dynamic updates provide a powerful and flexible means of update interface elements based on data changes on the server.  With careful judgement about what needs to be updated and how often, data viewed on a page will never be out of date.

Wednesday 28 September 2016

What is a TACTIC Widget?

Widgets are self-contained user interface elements.  In TACTIC, they combine HTML layout and CSS styling with server-side Python processing, client side Javascript behaviors, all encapsulated in a single entity called a widget.

Widgets are a fundamental building block to interfaces in TACTIC. Even the base HTML tags have been mapped to TACTIC Widgets, however, most users will create and combine higher level widgets using pure python classes or, more commonly, they can be created using the TACTIC Custom Layout Editor.

The custom layout editor enables the creation of widgets directly within the TACTIC interface. Very complex enterprise applications can be created using solely this editor without needing to resort to Python class (however, that is always an option, if desired)

There are 5 main components to a TACTIC Widget:

  1. Keyword Arguments
  2. HTML
  3. Styles
  4. Behaviors
  5. Python

Each of these components make up the definition of single widget.  They are form a single interface element. The simplest widget is a pure html element.


In order to style this element, CSS can be attached to this element.   Because this is HTML, you can add CSS styles directly on the element itself:

<div style=”color: #F00”>Hello</div>

A better method is to separate out the styling of the widget from the structure.   Although, an “id” could be used to relate CSS to this element, it is generally not recommended.  This is because “id” must be unique throughout the entire document.  With widgets as reusable elements that can appear many times in a single interface, it is better to identify an element using “class”.

<div class=”element1”>

And in the styles tab of the Custom Layout Editor, you would add:

.element1 {
   color: #F00;

TACTIC Widgets also bind server side processing into the widget.  The python component has a predefined kwargs variable that contains input arguments to the widget.  Thus in the python tab, you could write:

color = kwargs.get(“color”)

And you could use this variable in both the HTML tab and the styles tab.  For example:

.example1 {
   color: ${color};

This becomes a configurable widget that can be embedded into another widget using the HTML as follows:

 <h1>Reference to another widget</h1>
 <element view=”content1” color=”#0B0”/>

The  would embed the word “Hello” with blue text into this widget.

Finally, you can access the kwargs variable in javascript in the behaviors tab from the implicit bvr variable:

<behavior class=”.element1” event=”click”>
   var color = bvr.kwargs.color;

Using these together, you can build up more complex reusable widgets.

There are a number of significant advantages of using TACTIC for content delivery, despite the many web platforms out there and many that do a good job of delivering web content.  For those more comfortable with these other platforms, there is a TACTIC javascript standalone API which can be imported into any web framework.  

TACTIC widgets have a very strong encapsulation between the client side code and the server side code.  Data structures are seamless transferred and shared between the two. TACTIC widgets can be loaded asynchronously and do not rely on full page refresh to load content.  This makes TACTIC apps feel smoother because they only update parts of the interface that need updating.  TACTIC also has many built-in widgets that do common widgets that together provide a wide range of tools to handle many enterprise requirements, especially when creating data driven workflow solutions.