Windows Workflow Foundation: Software Bug Reporting Workflows

By Zoiner Tejada, Hershey Technologies

Articles in this series

Published: January, 2009

In many software shops, the bug tracking system lies at the heart of daily activity. It serves to coordinate the efforts of multiple people in differing roles (QA, managers, team leads, developers, customers, etc.) while they catalog, review and address issues and defects. In this article, we examine the implementation of a simplistic bug reporting workflow and show how to approach it using WF.

In the bug reporting workflow there are three roles:

  • QA – responsible for initially submitting a bug, as well as reviewing any bugs returned to them by a Development Manager or Developer. For any bug reports returned to QA, QA can either append notes prior to returning the bug report to the Development Manager, or QA can close the bug report.
  • Development Manager – focuses on triaging a bug. If the development manager can reproduce it, then it is assigned to a developer. Otherwise he might return it to QA for additional clarification or even close the bug if it is a duplicate, known to be obsolete or is actually functioning as designed.
  • Developer – reviews the bug reported and takes corrective action on the code and then reports completion.This amounts to adding some notes to the bug description and returning the bug report to QA for verification of the fix.

Figure 1 below illustrates the relationship between these roles and the actions they can take against a bug report.

Figure 1 - High level model of the simple bug report workflow.

Enhancing Transparency & Comprehension Of Workflow Designs

Before delving into the implementation of this scenario take notice of how, while reading the scenario description, you read both list of role descriptions and probably walked thru the various paths on the high level diagram. You could probably hand those two items over to a non-technical user and they would be able to understand the bug reporting process at that level of detail.  When actually implementing workflows it is important to consider this as well. Consider how comprehensible your workflows are to the non-technical user. The benefit of a comprehensible workflow design is transparency of implementation- that is non-technical users can understand what the workflow is doing, without having to be able to read code. This allows the realization of two workflow goals: auditability and verifiability. Auditability allows users to review a workflow that has already executed and understand what happened. Verifiability permits users, either during design or during system audits, to confirm that the implementation is true to the business requirements.

Audibility and verifiability is achieved using WF by a combination of good workflow design and the application of custom activity designers. Good workflow design means making sensible decisions on the granularity of activities in a workflow. Granularity asks if each activity really stand for one line of code or if instead it stands for a task from the perspective of a business user. Once the appropriate level of granularity is chosen, activity designers make it possible for each activity to be self describing of the task encapsulated. Take for example the following two sequences in Figure 2 which represent the same implementation at the same granularity. The one on the right makes use of a custom activity designer to communicate extra information valuable to a reader of the workflow design.

Figure 2 - Comparing identical flows without (left) and with (right) activity designers.

The following workflow implementations for the bug reporting scenario both use a custom designer whose implementation is also reviewed.

Creating the Sequential Version

The workflow begins when a QA user submits an initial bug report using the interface shown in Figure 3.

Figure 3 - UI before QA submits new bug report.

QA fills out the bug title and description and hits submit. This launches the workflow shown in Figure XX and covered in the text that follows.

Figure 4 - The Bug Report Workflow (Sequential Version)

Initially, the workflow will arrive at the assignToDevManager1 task, where a custom activity called AssignTo is configured to assign the bug report to a specific Development Manager. Following that assignment, the workflow waits for the Development Manager to take an action on the bug report (Approve, Return or Close). The user interface the Development Manager is presented with appears as in Figure 5.

Figure 5 - Development Manager Review UI.

Should the Development Manager choose Approve, then the sequence following the approve1 activity is executed which assigns the bug report to a developer and waits for the developer to return the bug to QA for verification (return2). Once the developer has done so using the user interface (see Figure 6) the workflow is assigned to QA (assignToQA2) and the workflow waits for QA to either re-submit the bug report indicating that the bug was not completely resolved or to close the bug report, indicating that it was fixed (see Figure 7).

Figure 6 - Developer Review UI.

If the Development Manager chooses to Return the bug report to QA (perhaps because he was unable to reproduce the bug or needs clarification), then return1 is executed followed by assignToQA1.  At this point the workflow waits for QA to either re-submit the bug report with the additional requested details or to close the bug report via the user interface shown in Figure 7.

Figure 7 - QA Review of Returned Bug Report.

If the Development Manager chooses to Close the bug report, then the close1 activity is executed in the rightmost event driven branch, and the workflow will complete because the condition evaluated by whileBugReportActive will evaluate to false (it checks the status of the bug report for a value of Closed).

So long as no one chooses to close the bug report, the WhileActivity will cause another iteration of the loop that begins with re-assigning the bug report to the Development Manager.

From a technical standpoint, the most interesting activity that is used in the sequential implementation is ListenActivity that encompasses all three major branches of execution. It is worth understanding how the ListenActivity functions in this scenario with respect to the way it handles the execution of its child activities (eventDrivenActivity1, eventDrivenActivity2, eventDrivenActivity3).  When the workflow executes listenActivity1, it is actively waiting for an “approve”, “return to” and “close” signals. However, once an individual signal has been received (e.g., “approve”) then only the branch that received that signal (e.g., eventDrivenActivity1) should be active. The remaining two (eventDrivenActivity2, eventDrivenActivity3) are effectively cancelled by a combination of factors: the user interface prevents the user from sending a “return to” signal by not displaying that button (which would inadvertently execute eventDrivenActivity2) and because close2 is the next activity executed in the sequence it will receive the “close” signal over close1.

It is important to note that a sequential workflow listens for all events in the workflow all the time, and if the workflow is not actively listening for the event received, it will queue it up. In this case, if the UI mistakenly allowed a “return to” signal to be sent when waiting at listenActivity2, and then allowed a “submit”, when the workflow loops it will execute return1 as if it had just received the “return to” signal.

Creating the State Machine Version

The bug report workflow is easily re-designed as a state machine. Figure 8 show the implementation at the highest level.

Figure 8 - Bug Report Workflow (State Machine Version)

The main difference between the sequential and state machine implementation is visible at the high level- no longer is there a while loop to control when QA returns the bug report to the Development Manager. Instead, this is described directly by a transition from onQASubmit (in the QAReview state) to the DevManagerReview state.

Drilling down, each event-driven sequence is really just the pair of activities from the sequential version that represent an action (such as approve1) and an assignment (such as assignToDeveloper1), followed by a SetState activity to cause a transition to the next state.  Figure 9 shows an example of this pattern as it occurs within the onDMApproved event driven sequence of the DevManagerReview state.

Figure 9 - Handling Development Manager Approval.

A quick glance at the document outline for this workflow should suffice to show how this pattern is repeated (see Figure 10).

Figure 10 - Outline of Bug Report Workflow.

This implementation serves to highlight a core difference in event handling between sequential and state machine workflows. Recall that in the sequential implementation, we were able to nest a ListenActivity within a ListenActivity- effectively nesting event-driven sequences.  In the state machine workflow, this is not allowed, so you end up with more event driven sequences at the top levels of the workflow definition (e.g., the sequential workflow defined three, the state machine workflow defines six).

Custom Activity Designer Implementation

Custom activity designers are classes that derive from ActivityDesigner (in the System.Workflow.ComponentModel.Design namespace). A custom activity designer provides control over a fairly broad swath of design time functionality. With a custom activity designer, you can:           

  • Control the physical appearance of the activity, such as enhancing it to support verbose user friendly labels or have a radically different appearance. You get complete control over how the activity is drawn on the design surface.
  • Affect the layout of the activity by dynamically computing its size.
  • Alter the properties that are exposed to the property grid.
  • Determine how the activity responds to drag and drop operations.
  • Configure Smart Tags.

For this scenario, we were most interested in the ability of an ActivityDesigner to control how the activities are displayed, and what information they convey to the viewer.  The implementation uses one ActivityDesigner derived class called BugActivityDesigner that overrides the default functionality for painting and layout.  Figure 11 shows two examples of the BugActivityDesigner in action.

Figure 11 - Two different appearances from the same activity designer.

The entire implementation of the designer is available (see the related links section). However it is worth pointing out the following elements of the ActivityDesigner that were overridden in the implementation:

  • OnLayoutSize – This method was overridden to measure the size of the Description property’s text and size the actual activity to provide enough space for the text to flow vertically as needed, up to a predetermined height.
  • OnActivityChanged – This method was overridden to respond to changes in value of the either the activity’s Name or Description properties, and force the activity to have its layout re-computed as the change in text content means the activity may have to shrink or grow to fit it.
  • OnPaint – This method is the workhorse of the BugActivityDesigner. It is responsible for drawing a gold-filled rectangle for event handling activities, or a white filled activity for all others. It draws the solid black border around the activity, the icon, writes the activity name in bold, right-aligned at the top of the activity and center aligns the description text, wrapping it to occupy as much of the activity’s body as possible. When there is too much text to fit within the maximum size of the activity, the method ensures the string ends with ellipses to indicate that some text was truncated.

The aforementioned covered custom activity designers in the context of how the help after the workflow has been designed. There are however, quite a few designer features that improve the workflow design experience. While they are not the focus of this article, Table XX below introduces them because they are a mandatory part of any good workflow design.

  • ActivityToolboxItem – Allows one to specify a bitmap that will appear for toolbox only. More importantly it provides the ability to show a custom user interface dialog window when the activity is dragged & dropped from toolbox. Alternately, it can also be used to execute code when the activity is dragged & dropped from the toolbox. Both are useful when certain settings must be made before the activity is fully usable from the design surface or when you want to automatically configure some property bindings. For example, in the Bug Workflow, you might configure the AssignToAcitivty to automatically bind its BugReport property to the BugReport property on the workflow when dragged from the toolbox. In addition, this is where one can automatically add default child activities to a composite activity dragged from the toolbox, such as the default number of branches in a branching activity.
  • ToolboxBitmapAttribute - This one simply provides an easy way to specify a bitmap for the toolbox & design surface.
  • ActivityValidator – Activity Validators allow you to check the configuration of the activity at build time and show the appropriate errors or warning to the designer through the Errors window and Smart Tags.
  • Type Converters – Enhance the property setting experience by allowing users to strings in the property grid and have them converted to the appropriate complex values automatically. For example, an activity that works with a database might have a connection string property that can take in a full string containing server, database and user information, but in reality each is stored in its own property on the activity.
  • ExpandableObjectConverter – These enhance the property editing experience by allowing you drill into complex types and set the values for sub-properties of those complex types.
  • EditorAttribute – For any moderately configurable activity, custom editors are a must. These allow you to display rich editing panels either directly over the property grid in a drop down or as a modal dialog.