Write your first ftrack Action

Don’t know what Actions are? Read more about Actions here!
You can download sample Actions from our Action repository on Bitbucket. You can also visit the Actions sections on our ftrack community forum.

Lets implement an Action that takes a selection, write out the name of the selection in a HTML file and attach the file to the users Job list.

First, let the Action know which server to contact, which API key to use and a valid ftrack username.
Note: this can be setup in many different ways, but for simplicity to get started with the script we’re adding it to to the file instead of a global environment setting.

# :coding: utf-8
import os
import logging
import uuid

os.environ["FTRACK_SERVER"] = "https://YOUR_SERVER.ftrackapp.com"
os.environ["FTRACK_APIKEY"] = "YOUR_API_KEY"
os.environ["LOGNAME"] = "VALID_FTRACK_USERNAME"

import ftrack

 

Next, create a Class that inherits ftrack.Action. Also specify a label and a unique identifier that will be used later on.

class Example(ftrack.Action):
    '''A simple Action example'''

    label = 'My Example Action'
    identifier = 'com.ftrack.{0}'.format(str(uuid.uuid1()))

 

The function discover() gets invoked when a user tries to bring up the Action menu to list available Actions. You can override the discover() function in your class that inherits ftrack.Action. By doing so you have control on where your action gets invoked (Context, Selection mode or User permissions for example). In this example we’re restricting our selfs to a single selection, and the Action will not be visible if the selection is a User ()that we are not allowed to invoke this action if we clicked on a User. We’re also specifying an icon. If you don’t, the system will render a default icon for you based in the initials in your label.

    def discover(self, event):
        '''Return action config if triggered on a single selection.'''
        data = event['data']

        # If selection contains more than one item return early since
        # this action will only handle a single version.
        selection = data.get('selection', [])
        entityType = selection[0]['entityType']
        self.logger.info('Got selection: {0}'.format(selection))
        if len(selection) != 1 or entityType == 'user':
            return

        return {
            'items': [{
                'label': self.label,
                'actionIdentifier': self.identifier,
                'icon':"https://www.ftrack.com/wp-content/uploads/reports1.png"
            }]
        }

 

Launch is invoked when a user click on your Action. Here we can return a UI to request more information from the user. Read more about UI for Actions.  In this example we are requesting a color selection from the user. If no color selection is provided, the default will be returned, which is currently set to green (‘value’ : ‘green’). If When we’ve got input from the user we can call doSomething() with the values from the UI and do the actual action. In a production environment you would probably want to thread this so that multiple user can use the Action at the same time. Please see code examples of this in our Actions code repository (decorator async). This will be part of the framework in the future.

    def launch(self, event):
        # get id of user invoking this action
        userId = event['source']['user']['id']
        data = event['data']
        selection = data.get('selection', [])
        entityId = selection[0]['entityId']
        entityType = selection[0]['entityType']
        entity = getEntity(entityType=entityType, entityId=entityId)
        if 'values' in event['data']:
            # Do something with the values or return a new form.
            values = event['data']['values']
            ftrack.EVENT_HUB.publishReply(
                event,
                data={
                    'success': True,
                    'message': 'Action was successful'
                }
            )
            doSomething(userId=userId, entityType=entityType, entity=entity, values=values)
            return

        return {
            'items': [
                {
                    'type': 'label',
                    'value': 'Your selection: {0}'.format(entity.get('name'))
                }, {
                    'type': 'label',
                    'value': '___'
                }, {
                    'label': 'Select color',
                    'type': 'enumerator',
                    'name': 'color',
                    'value':'green',
                    'data': [
                    {
                    'label': 'Green',
                    'value': 'green'
                }, {
                    'label': 'Blue',
                    'value': 'blue'
                }]
                }
            ]
        }

 

The only thing that doSomething() does right now is to add a record to the users job list, set it to “done”, and set the description to the color that was passed from the UI. As you can it’s easy to integrate with the built in Job list, just call ftrack.createJob() with the user id of the user running the Action (or another user id if you want to add it to another users job list), a description and current status of the job.

def doSomething(userId=None, entityType=None, entity=None, values=None):
    description = u'Running Example Job'
    job = ftrack.createJob(
        description=description,
        status='running',
        user=userId
    )
    color=values['color']
    job.setStatus('done')
    job.setDescription("You color is {0}".format(color))

 

Lets make it a bit more interesting by also creating a html file, and add it as a attachment to the record in the users job list. I’m uploading the file with Job.createAttachment()

def doSomething(userId=None, entityType=None, entity=None, values=None):
    description = u'Running Example Job'
    job = ftrack.createJob(
        description=description,
        status='running',
        user=userId
    )
    try:
        color=values['color']

        html = "\
                <html>\
                    <head>\
                        <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>\
                        <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css'>\
                    </head>\
                    <body>\
                       <h3>My Example Selection <span class='label {0}''>{1}</span></h3>\
                    </body>\
                </html>".format('label-success' if color == 'green' else 'label-info', entity.get('name'))

        # have a html file to attach to job list
        filename = "example-{0}.html".format(str(uuid.uuid1()))
        f= open(filename,"w")
        f.write(html)
        f.close()

        job.createAttachment(filename, fileName=filename)
        job.setStatus('done')
        job.setDescription("You color is {0}".format(color)) 
        os.remove(filename)
    except:
        job.setStatus('failed')

 

This is the final code. Follow the install instructions above to have it running on your machine.

# :coding: utf-8
import os
import logging
import uuid

os.environ["FTRACK_SERVER"] = "https://YOUR_SERVER.ftrackapp.com"
os.environ["FTRACK_APIKEY"] = "YOUR_API_KEY"
os.environ["LOGNAME"] = "VALID_FTRACK_USERNAME"

import ftrack

def getEntity(entityType=None, entityId=None):
    ''' A helper to get Entity object '''
    if entityType is None or entityId is None:
        return None
    if entityType == 'user':
        return ftrack.User(entityId)
    if entityType == 'show':
        return ftrack.Project(entityId)
    elif entityType == 'task':
        return ftrack.Task(entityId)
    elif entityType == 'list':
        return ftrack.List(entityId)
    elif entityType == 'reviewsession':
        return ftrack.ReviewSession(entityId)
    else:
        return None

def doSomething(userId=None, entityType=None, entity=None, values=None):
    description = u'Running Example Job'
    job = ftrack.createJob(
        description=description,
        status='running',
        user=userId
    )
    try:
        color=values['color']

        html = "\
                <html>\
                    <head>\
                        <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>\
                        <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css'>\
                    </head>\
                    <body>\
                       <h3>My Example Selection <span class='label {0}''>{1}</span></h3>\
                    </body>\
                </html>".format('label-success' if color == 'green' else 'label-info', entity.get('name'))

        # have a html file to attach to job list
        filename = "example-{0}.html".format(str(uuid.uuid1()))
        f= open(filename,"w")
        f.write(html)
        f.close()

        job.createAttachment(filename, fileName=filename)
        job.setStatus('done')
        job.setDescription("You color is {0}".format(color)) 
        os.remove(filename)
    except:
        job.setStatus('failed')

class Example(ftrack.Action):
    '''A simple Action example'''

    label = 'My Example Action'
    identifier = 'com.ftrack.{0}'.format(str(uuid.uuid1()))

    def discover(self, event):
        '''Return action config if triggered on a single selection.'''
        data = event['data']

        # If selection contains more than one item return early since
        # this action will only handle a single version.
        selection = data.get('selection', [])
        entityType = selection[0]['entityType']
        self.logger.info('Got selection: {0}'.format(selection))
        if len(selection) != 1 or entityType == 'user':
            return

        return {
            'items': [{
                'label': self.label,
                'actionIdentifier': self.identifier,
                'icon':"https://www.ftrack.com/wp-content/uploads/reports1.png"
            }]
        }

    def launch(self, event):
        # get id of user invoking this action
        userId = event['source']['user']['id']
        data = event['data']
        selection = data.get('selection', [])
        entityId = selection[0]['entityId']
        entityType = selection[0]['entityType']
        entity = getEntity(entityType=entityType, entityId=entityId)
        if 'values' in event['data']:
            # Do something with the values or return a new form.
            values = event['data']['values']
            ftrack.EVENT_HUB.publishReply(
                event,
                data={
                    'success': True,
                    'message': 'Action was successful'
                }
            )
            doSomething(userId=userId, entityType=entityType, entity=entity, values=values)
            return

        return {
            'items': [
                {
                    'type': 'label',
                    'value': 'Your selection: {0}'.format(entity.get('name'))
                }, {
                    'type': 'label',
                    'value': '___'
                }, {
                    'label': 'Select color',
                    'type': 'enumerator',
                    'name': 'color',
                    'value':'green',
                    'data': [
                    {
                    'label': 'Green',
                    'value': 'green'
                }, {
                    'label': 'Blue',
                    'value': 'blue'
                }]
                }
            ]
        }

def main():
    '''Register action and listen for events.'''
    logging.basicConfig(level=logging.INFO)

    ftrack.setup()
    action = Example()
    action.register()

    ftrack.EVENT_HUB.wait()

if __name__ == '__main__':
    main()

Join the discussion 3 Comments

  • Mike says:

    The installation instructions are too vague. There should be an absolute beginner version that explains exactly how to get an app up and running. I’m not a total beginner, but I also can’t get any actions up and running with the current tutorial.

    • Fredrik Limsater says:

      Thanks for the feedback Mike. Will let the team know so we can improve. /Fredrik

  • Diana Naranjo Pomalaya says:

    Is there a way to make the boolean’s width larger? Right now the checkbox appears on top of my label

Leave a Reply