Changes between Version 2 and Version 3 of TestManagerForTracPluginWorkflow


Ignore:
Timestamp:
Oct 9, 2010 4:39:33 PM (4 years ago)
Author:
seccanj
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • TestManagerForTracPluginWorkflow

    v2 v3  
    11= Test Manager for Trac - Generic Workflow Engine = 
    22 
    3  
    4  
    5  
    6 '''To be documented.''' 
     3The Test Manager plugin is comprised of four plugins, one of which is a Generic Workflow Engine plugin for any Trac resource. 
     4 
     5The 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. 
     6 
     7Every 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. 
     8 
     9 
     10== How to implement Workflow on Test Cases == 
     11 
     12The following is a cookbook to implement Workflow on Test Cases. 
     13 
     14=== 1) Declare the desired workflow steps === 
     15 
     16First thing to do is to define the "state machine". Think of it as the definition of the steps your artifacts should go through in their transitions between a state and another. 
     17 
     18A state machine indeed defines three things: 
     19 * States - the 'steps' of the workflow. Note that the first step of any workflow will always be "new". 
     20 * Transition actions - the User actions avaiable in each state, which may optionally move the artifact from that state to another. 
     21 * Operations - what the system must perform on the artifact (but not only) when a certain transition action is triggered. 
     22  
     23The three elements above are specified in the trac.ini file with the following syntax. 
     24 
     25=== Definition of a workflow for a resource type === 
     26 
     27First of all you should define that a particular resource type, e.g. Test Cases, should be managed through a workflow. 
     28 
     29To do this, you must add to the trac.ini file a new section, named after the resource type followed by "-resource_workflow". 
     30 
     31For resource type we mean the "realm" string used for resource registration into the Trac environment. 
     32 
     33Resource type names for the Test Manager plugin artifacts are the following: 
     34 * testcatalog 
     35 * testcase 
     36 * testcaseinplan 
     37 * testplan 
     38 
     39For example, to start the definition of a state machine for Test Case, add the following into the trac.ini file: 
     40[testcase-resource_workflow] 
     41 
     42  
     43=== Definition of transition actions and states: === 
     44 
     45In the section started with the row above you can define the state machine for the specified artifact. 
     46 
     47To 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: 
     48 
     49{{{ 
     50  '''action''' = '''old state''' -> '''new state''' 
     51}}} 
     52 
     53This is sufficient to: 
     54 * Define a state named '''old state''' 
     55 * Define a state named '''new state''' 
     56 * 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'''. 
     57  
     58Special cases: 
     59 * 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). 
     60 * 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". 
     61 
     62 
     63=== Definition of operations to be performed along with specified actions === 
     64 
     65You 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. 
     66 
     67There is a set of [wiki:TestManagerPluginWorkflowOperations out-of-the-box operations] available, but more operations can be provided with your or other plugins by implementing the IWorkflowOperationProvider interface. 
     68 
     69To specify one or more operations to be performed along with a transition action, use the following syntax: 
     70 
     71{{{ 
     72  '''action'''.operations = '''operation1''','''operation2''',... 
     73}}} 
     74 
     75This specifies that the operations named '''operation1''' etcetera should be performed after the User has chosen the transition action named '''action'''. 
     76 
     77You can specify one or more operations this way, separating them with commas. 
     78 
     79=== Other properties === 
     80 
     81There are other optional properties of a workflow that let you customize its behavior. 
     82 
     83 * 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: 
     84   '''action'''.permissions = '''permission1''','''permission2''',... 
     85  
     86  
     87=== Example === 
     88 
     89This is a sample content of the trac.ini file to associate a workflow to the Test Case object. 
     90 
     91The workflow is shown in the following figure: 
     92 
     93[[BR]] 
     94[[BR]] 
     95[[Image(sample_workflow.png)]] 
     96[[BR]] 
     97'''Sample workflow for Test Cases''' 
     98[[BR]] 
     99[[BR]] 
     100 
     101{{{ 
     102  [testcase-resource_workflow] 
     103  sleep = new -> asleep 
     104  sleep.permissions = TEST_MODIFY 
     105 
     106  sing = new -> singing 
     107  sing.permissions = TEST_MODIFY 
     108  sing.operations = sample_operation 
     109 
     110  calmdown = singing -> calm 
     111  calmdown.permissions = TEST_MODIFY 
     112 
     113  kill = asleep,calm -> dead 
     114  kill.permissions = TICKET_MODIFY 
     115}}} 
     116 
     117=== 2) Implement any custom operations === 
     118 
     119A set of predefined, [wiki:TestManagerPluginWorkflowOperations out-of-the-box operations] is available, but you may want to create your own operations to fit your needs. 
     120 
     121To do this, you must write python code into a plugin, and deploy it along with the TracGenericClass and TracGenericWorkflow plugins.  
     122 
     123To be able to provide custom workflow operations, your Trac Component must implement the IWorkflowOperationProvider interface. 
     124 
     125Let's take a look at a sample operation provider, which is included in the TestManager plugin. 
     126 
     127{{{ 
     128from tracgenericworkflow.api import IWorkflowOperationProvider 
     129 
     130class TestManagerWorkflowInterface(Component): 
     131    """Adds workflow capabilities to the TestManager plugin.""" 
     132     
     133    implements(IWorkflowOperationProvider) 
     134 
     135    # IWorkflowOperationProvider methods 
     136    # Just a sample operation 
     137    def get_implemented_operations(self): 
     138        yield 'sample_operation' 
     139 
     140    def get_operation_control(self, req, action, operation, res_wf_state, resource): 
     141        if operation == 'sample_operation': 
     142            id = 'action_%s_operation_%s' % (action, operation) 
     143            speech = 'Hello World!' 
     144 
     145            control = tag.input(type='text', id=id, name=id,  
     146                                    value=speech) 
     147            hint = "Will sing %s" % speech 
     148             
     149            return control, hint 
     150         
     151        return None, '' 
     152         
     153    def perform_operation(self, req, action, operation, old_state, new_state, res_wf_state, resource): 
     154        self.log.debug("---> Performing operation %s while transitioning from %s to %s." 
     155            % (operation, old_state, new_state)) 
     156 
     157        speech = req.args.get('action_%s_operation_%s' % (action, operation), 'Not found!') 
     158 
     159        self.log.debug("        The speech is %s" % speech) 
     160}}} 
     161 
     162 
     163As you can see, it's not much code to write. Let's go through it. 
     164 
     165The IWorkflowOperationProvider interface prescribes three methods that a provider must implement: 
     166 
     167 * get_implemented_operations(): Used by the workflow engine to ask the provider the names of the operations it provides. 
     168 
     169   * This method must return a basestring generator with the operation names. 
     170 
     171 * 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. 
     172 
     173   The workflow engine passes to the provider: 
     174   * The http request. 
     175   * The name of the transition action that is associated with this operation in the trac.ini file. 
     176   * The name of the operation to be rendered (this in case the provider has stated to provide more than once). 
     177   * The ResourceWorkflowState object representing the current state of the resource in the workflow. 
     178   * The Resource object representing the artifact instance being subject to the workflow. 
     179    
     180   This method must return two results: 
     181   * 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. 
     182   * A string, containing a textual short description of what the operation does. 
     183 
     184 * 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. 
     185 
     186   The workflow engine passes to the provider: 
     187   * 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. 
     188   * The name of the transition action that is associated with this operation in the trac.ini file. 
     189   * The name of the operation to be performed (this in case the provider has stated to provide more than once). 
     190   * The name of the old workflow state from which the artifact is being moved. 
     191   * The name of the new workflow state to which the artifact is being moved. 
     192   * The ResourceWorkflowState object representing the current state of the resource in the workflow. 
     193   * The Resource object representing the artifact instance being subject to the workflow. 
     194  
     195   There are no requirements as to what the provider must or can do inside this method, except from what's specified next.  
     196   It is '''NOT allowed''' to modify the resource object. 
     197   It is '''NOT allowed''' to modify the res_wf_state object. 
     198    
     199    
     200=== 3) Add workflow support to any of your artifacts === 
     201 
     202Test 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. 
     203 
     204But 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. 
     205 
     206To do that, you should do the following steps: 
     207 
     208 1. Define your desired workflow - explained above 
     209 1. Define any custom operations you may provide on your artifacts - explained above 
     210 1. '''Display the workflow markup into your web pages'''. This is what this section is about. 
     211 
     212There 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. 
     213 
     214To do this, you must: 
     215  
     216 1. Implement the ITemplateStreamFilter interface 
     217 1. 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. 
     218  
     219You may ask... this is it??? 
     220 
     221Yes! You don't have to do anything to: 
     222 * Manage you resource states,  
     223 * Handle transitions, 
     224 * Manage operations 
     225 
     226all this is automatically procided by the plugin. 
     227 
     228So, let's take a look at how the TestManager plugin incorporates this web interface support. 
     229 
     230{{{ 
     231from trac.web.api import ITemplateStreamFilter 
     232from tracgenericworkflow.api import ResourceWorkflowSystem 
     233 
     234class TestManagerWorkflowInterface(Component): 
     235    """Adds workflow capabilities to the TestManager plugin.""" 
     236     
     237    implements(ITemplateStreamFilter) 
     238 
     239    # ITemplateStreamFilter methods 
     240    def filter_stream(self, req, method, filename, stream, data): 
     241        page_name = req.args.get('page', 'WikiStart') 
     242        planid = req.args.get('planid', '-1') 
     243 
     244        if page_name == 'TC': 
     245            # The root catalog does not have workflows 
     246            return stream 
     247 
     248        if page_name.startswith('TC') and filename == 'wiki_view.html': 
     249            req.perm.require('TEST_VIEW') 
     250             
     251            # Determine which object is being displayed (i.e. realm),  
     252            # based on Wiki page name and the presence of the planid  
     253            # request parameter. 
     254            realm = None 
     255            if page_name.find('_TC') >= 0: 
     256                if not planid or planid == '-1': 
     257                    realm = 'testcase' 
     258                    key = {'id': page_name.rpartition('_TC')[2]} 
     259                else: 
     260                    realm = 'testcaseinplan' 
     261                    key = {'id': page_name.rpartition('_TC')[2], 'planid': planid} 
     262            else: 
     263                if not planid or planid == '-1': 
     264                    realm = 'testcatalog' 
     265                    key = {'id': page_name.rpartition('_TT')[2]} 
     266                else: 
     267                    realm = 'testplan' 
     268                    key = {'id': planid} 
     269 
     270            id = get_string_from_dictionary(key) 
     271            res = Resource(realm, id) 
     272 
     273            workflow_markup = ResourceWorkflowSystem(self.env).get_workflow_markup(req, '..', realm, res) 
     274             
     275            return stream | Transformer('//div[contains(@class,"wikipage")]').after(workflow_markup)  
     276 
     277        return stream 
     278}}} 
     279 
     280Let's take a look at the ResourceWorkflowSystem.get_workflow_markup() method. 
     281 
     282{{{ 
     283  get_workflow_markup(self, req, base_href, realm, resource): 
     284}}} 
     285 
     286It takes the following arguments: 
     287 
     288 * req: the http request 
     289 * 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. 
     290 * realm: the artifact's resource type - e.g. 'testcase' in the examples above. 
     291 * resource: the actual Trac resource instance object of the workflow. 
     292 
     293Note: 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. 
     294This is why you see the following code, where to build a Trac Resource corresponding to a TestManager aretifact, I first build the Resource ID as the string representation of the artifact's key properties, then use it to create the Resource: 
     295 
     296{{{ 
     297id = get_string_from_dictionary(key) 
     298res = Resource(realm, id) 
     299}}}