wiki:TestManagerForTracPluginWorkflow

Version 10 (modified by Roberto Longobardi, 13 years ago) (diff)

--

Test Manager for Trac - Generic Workflow Engine

The Test Manager plugin is comprised of four plugins, one of which is a Generic Workflow Engine plugin for any Trac resource.

The following figure shows a sample workflow added to Test Cases with custom sample operations. No built-in operation is currently implemented but the sample one shown here, named 'sample_operation', which logs a debug message with the text input by the User.

Every object which has a workflow defined is created in a "new" state, so every transition should consider this as the first state in the state machine.

Example: How to implement Workflow on Test Cases

The following is a cookbook to implement Workflow on Test Cases.

All is required it to define the desired workflow steps, a.k.a. the "state machine", in the trac.ini file. A "state machine" is just a definition of the steps (known as states) your artifacts should go through after they are created, of the actions that are allowed in each state and that may transition the artifact into a different state, and of the operations the system should perform on the artifact, or on anything else, along with these actions.

A state machine indeed defines three things:

  • States - the 'steps' of the workflow. Note that the first step of any workflow will always be "new".
  • Transition actions - the User actions avaiable in each state, which may optionally move the artifact from that state to another.
  • Operations - what the system must perform on the artifact (but not only) when a certain transition action is triggered.

The three elements above are specified in the trac.ini file with the following syntax.

Definition of a workflow for a resource type

First of all you should define that a particular resource type, e.g. Test Cases, should be managed through a workflow.

To do this, you must add to the trac.ini file a new section, named after the resource type followed by "-resource_workflow".

For resource type we mean the "realm" string used for resource registration into the Trac environment.

Resource type names for the Test Manager plugin artifacts are the following:

  • testcatalog
  • testcase
  • testcaseinplan
  • testplan

For example, to start the definition of a state machine for Test Case, add the following into the trac.ini file: [testcase-resource_workflow]

Definition of transition actions and states:

In the section started with the row above you can define the state machine for the specified artifact.

To define states of the workflow and actions that allow for moving artifacts from one state to another - so said transition actions, use the following syntax:

  <action> = <old state> -> <new state>

This is sufficient to:

  • Define a state named old state
  • Define a state named new state
  • Define a transition action action that should be available in the state old state and that, when chosen by the User, will move the artifact into the new state.

Special cases:

  • You can specify that an action should be available in all states by using the "* -> *" syntax for the state transition part (i.e. right side of the equal sign).
  • You can specify that an action should be available in more than one state by separating them with a comma, as in "asleep,calm -> dead".

Definition of operations to be performed along with specified actions

You can specify that every time an artifact is moved from one state to another by means of a transition action, the system should perfomr one or more operations.

There is a set of out-of-the-box operations available, but more operations can be provided with your or other plugins by implementing the IWorkflowOperationProvider interface.

To specify one or more operations to be performed along with a transition action, use the following syntax:

  <action>.operations = <operation1>,<operation2>,...

This specifies that the operations named operation1 etcetera should be performed after the User has chosen the transition action named action.

You can specify one or more operations this way, separating them with commas.

Other properties

There are other optional properties of a workflow that let you customize its behavior.

  • Permissions: you can specify which permissions a User must have to be able to fire any particular transition action. To do this, use the following syntax: action.permissions = permission1,permission2,...


Defining your desired workflow in the trac.ini file is all that is required to mandate a workflow to the artifacts provided with the TestManager plugin.


Example

This is a sample content of the trac.ini file to associate a workflow to the Test Case object.

The workflow is shown in the following figure:



A sample workflow
Sample workflow for Test Cases

  [testcase-resource_workflow]
  sleep = new -> asleep
  sleep.permissions = TEST_MODIFY

  sing = new -> singing
  sing.permissions = TEST_MODIFY
  sing.operations = sample_operation

  calmdown = singing -> calm
  calmdown.permissions = TEST_MODIFY

  kill = asleep,calm -> dead
  kill.permissions = TEST_MODIFY



How to provide custom operations

A set of predefined, out-of-the-box operations is available, but you may want to create your own custom operations to fit your needs.

To do this, you must write python code into a plugin, and deploy it along with the TracGenericClass and TracGenericWorkflow plugins.

To be able to provide custom workflow operations, your Trac Component must implement the IWorkflowOperationProvider interface.

Let's take a look at a sample operation provider, which is included in the TestManager plugin.

from tracgenericworkflow.api import IWorkflowOperationProvider

class TestManagerWorkflowInterface(Component):
    """Adds workflow capabilities to the TestManager plugin."""
    
    implements(IWorkflowOperationProvider)

    # IWorkflowOperationProvider methods
    # Just a sample operation
    def get_implemented_operations(self):
        yield 'sample_operation'

    def get_operation_control(self, req, action, operation, res_wf_state, resource):
        if operation == 'sample_operation':
            id = 'action_%s_operation_%s' % (action, operation)
            speech = 'Hello World!'

            control = tag.input(type='text', id=id, name=id, 
                                    value=speech)
            hint = "Will sing %s" % speech
            
            return control, hint
        
        return None, ''
        
    def perform_operation(self, req, action, operation, old_state, new_state, res_wf_state, resource):
        self.log.debug("---> Performing operation %s while transitioning from %s to %s."
            % (operation, old_state, new_state))

        speech = req.args.get('action_%s_operation_%s' % (action, operation), 'Not found!')

        self.log.debug("        The speech is %s" % speech)

As you can see, it's not much code to write. Let's go through it.

The IWorkflowOperationProvider interface prescribes three methods that a provider must implement:

  • get_implemented_operations(): Used by the workflow engine to ask the provider the names of the operations it provides.
  • This method must return a basestring generator with the operation names.
  • get_operation_control(req, action, operation, res_wf_state, resource): Called right before the web user interface for one of the operations the provider supports must be displayed.

The workflow engine passes to the provider:

  • The http request.
  • The name of the transition action that is associated with this operation in the trac.ini file.
  • The name of the operation to be rendered (this in case the provider has stated to provide more than once).
  • The ResourceWorkflowState object representing the current state of the resource in the workflow.
  • The Resource object representing the artifact instance being subject to the workflow.

This method must return two results:

  • A Genshi tag, containing the markup to render the controls necessary to let the User give any parameter for the operation. This controls will be put inside a form, and they will be available to retrieve User input by means of the request parameters in the perform_operation() method, described next.
  • A string, containing a textual short description of what the operation does.
  • perform_operation(req, action, operation, old_state, new_state, res_wf_state, resource): Called after the User has selected the specified operation, to actually perform it.

The workflow engine passes to the provider:

  • The http request, from which the provider can retrieve form values for the input fields it may have provided with the get_operation_control() method.
  • The name of the transition action that is associated with this operation in the trac.ini file.
  • The name of the operation to be performed (this in case the provider has stated to provide more than once).
  • The name of the old workflow state from which the artifact is being moved.
  • The name of the new workflow state to which the artifact is being moved.
  • The ResourceWorkflowState object representing the current state of the resource in the workflow.
  • The Resource object representing the artifact instance being subject to the workflow.

There are no requirements as to what the provider must or can do inside this method, except from what's specified next. It is NOT allowed to modify the resource object. It is NOT allowed to modify the res_wf_state object.

This is it. You have added a workflow and your custom operations to the Test Case artifact.

Of course, you can do the same with the three other artifacts in the TestManager plugin, namely Test Catalogs, Test Plans and Test Cases in Plan (i.e. with a status).



Extend workflow support to any of your artifacts

As said, Test artifacts already have workflow support. To turn it on, all you must do is define the desired workflow in the trac.ini file, as described above.

But there is more you can do with the TracGenericWorkflow standalone plugin (remember it requires TracGenericClass plugin to be installed, anyway), you can add workflow support to any of your artifacts inside Trac.

To do that, you should do the following steps:

  1. Define your desired workflow - explained above
  2. Define any custom operations you may provide on your artifacts - explained above
  3. Display the workflow markup into your web pages. This is what this section is about.

There are several ways how you can provide markup to web pages in Trac. Here, I'll explain the way I do it in the TestManager plugin, which is by means of the ITemplateStreamFilter.

To do this, you must:

  1. Implement the ITemplateStreamFilter interface
  2. When you need to display the markup for the workflow support - i.e. the list of available transition actions in the current resource state and the associated operations - call the ResourceWorkflowSystem.get_workflow_markup() method to get the markup, then just add it to your page.

You may ask... this is it???

Yes! You don't have to do anything to:

  • Manage you resource states
  • Handle transitions
  • Manage operations

all this is automatically performed by the plugin.

So, let's take a look at how the TestManager plugin incorporates this web interface support.

from trac.web.api import ITemplateStreamFilter
from tracgenericworkflow.api import ResourceWorkflowSystem

class TestManagerWorkflowInterface(Component):
    """Adds workflow capabilities to the TestManager plugin."""
    
    implements(ITemplateStreamFilter)

    # ITemplateStreamFilter methods
    def filter_stream(self, req, method, filename, stream, data):
        page_name = req.args.get('page', 'WikiStart')
        planid = req.args.get('planid', '-1')

        if page_name == 'TC':
            # The root catalog does not have workflows
            return stream

        if page_name.startswith('TC') and filename == 'wiki_view.html':
            req.perm.require('TEST_VIEW')
            
            # Determine which object is being displayed (i.e. realm), 
            # based on Wiki page name and the presence of the planid 
            # request parameter.
            realm = None
            if page_name.find('_TC') >= 0:
                if not planid or planid == '-1':
                    realm = 'testcase'
                    key = {'id': page_name.rpartition('_TC')[2]}
                else:
                    realm = 'testcaseinplan'
                    key = {'id': page_name.rpartition('_TC')[2], 'planid': planid}
            else:
                if not planid or planid == '-1':
                    realm = 'testcatalog'
                    key = {'id': page_name.rpartition('_TT')[2]}
                else:
                    realm = 'testplan'
                    key = {'id': planid}

            id = get_string_from_dictionary(key)
            res = Resource(realm, id)

            workflow_markup = ResourceWorkflowSystem(self.env).get_workflow_markup(req, '..', realm, res)
            
            return stream | Transformer('//div[contains(@class,"wikipage")]').after(workflow_markup) 

        return stream

Let's take a look at the ResourceWorkflowSystem.get_workflow_markup() method.

  get_workflow_markup(self, req, base_href, realm, resource):

It takes the following arguments:

  • req: the http request
  • base_href: an href string pointing to the base of your project - e.g. in the context of a wiki page, '..' returns up to your project's base URL.
  • realm: the artifact's resource type - e.g. 'testcase' in the examples above.
  • resource: the actual Trac resource instance object of the workflow.

Note: TestManager artifact resources are made so that their resource ID is a string representation of their key properties, in the form of a JSON dictionary. This is why you see the following code, where to build a Trac Resource corresponding to a TestManager artifact, I first build the Resource ID as the string representation of the artifact's key properties, then use it to create the Resource:

  id = get_string_from_dictionary(key)
  res = Resource(realm, id)

Attachments (1)

Download all attachments as: .zip