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.
.jpg)
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.
.gif)
.gif)
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.
.jpg)
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.
.jpg)
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.
.jpg)
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).
.jpg)
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.
.jpg)
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.
.jpg)
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.
.jpg)
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).
.jpg)
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.
.gif)
.gif)
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.
Other Design Related Items
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.
Related Links