Product Documentation

Grouped Forward Path reports

Mar 28, 2017

This topic provides an overview of the advanced Forward Path functionality that you can use to group applications by categories defined in the script (such as suitability for deployment on a particular platform) or by the AppDNA application group to which the applications belong. You can aggregate application values at the group level – for example, to create subtotals for each group – and create totals at the report level. This means, for example, that if application groups represent business units, you can create a Forward Path report that groups the applications by business unit and shows subtotals for each one.

Overview

The following image shows a snippet of a Forward Path report in which the applications have been grouped based on their RAG status for the Windows 7 and App-V reports. For each group, the total cost of remediation is shown, along with a count of the applications in the group. You can expand each group to view the applications inside. The total row at the top shows the totals for the entire report.

We will build up the Forward Path scenario that generated this report in the examples that follow. In order to make the examples as simple as possible, we will use a hard-coded remediation cost for each application. In real life, you would probably calculate this based on the application complexity and the remediation that is required. For an example that calculates costs in this way, see Use Effort Calculator variables in a Forward Path scenario.

The scenario functions

The scenario for a grouped Forward Path report typically implements the following standard Forward Path functions:

  • Initialize() – AppDNA automatically calls this function once at the start of the script. Typically this function initializes variables.
  • ForwardPath() – This is the standard function that all Forward Path scenarios must implement. AppDNA calls this function once for every application that is selected when you run the report. In a grouped Forward Path report, this function creates categories (called report groups) into which the applications are placed. The report groups can be created by the logic in the code or they can be based on application groups to which the applications already belong.
  • ProcessGroups() – AppDNA calls this function once for every report group that is generated by the ForwardPath() function. This function defines the group-level report columns and the values they contain.
  • GetGroupColumnSummary() – AppDNA calls this function once for each column defined in the ProcessGroups() function. Typically this function defines the report-level totals.
  • OnProcessingComplete() – This is an optional function that AppDNA calls once after all the rest of the processing is complete. You can use this function to perform any final processing before the report is displayed.

The following diagram shows how AppDNA calls the functions when you run the report. The diagram is followed by the specification of each of the advanced functions, along with an example that together create the report shown in the Overview section above.

Note: Some of the examples in this topic use the underscore (_) notation to break long lines into two or more lines. This is so that the example code snippets render correctly when included in a PDF. See the MSDN Library for more on this notation.

Initialize()

If specified in the scenario, AppDNA automatically calls the Initialize() function once at the start of the script. The Initialize() function must have the following signature:

Public Overrides Sub Initialize() 
   ' Enter your code here. 
End Sub

In this example we will use the Initialize() function to initialize variables that we will use to store application values so that they can be aggregated at the group level. However, first we must declare the variables that we want to use throughout the scenario. Because we will use these variables in more than one function, we will define them at the start of the script (before the Initialize() function), like this:

' Declare a Dictionary variable to store the remediation 
' costs for each application in the group. 
Private Dim costs As Dictionary(Of String, List(Of Integer)) _ 
   = New Dictionary(Of String, List(Of Integer)) 
 
' Declare string variables that store the names of the  
' report groups. 
Private Dim win7group As String = "Windows 7" 
Private Dim appvgroup As String = "App-V" 
Private Dim othergroup As String = "Retire"

In this example, we have declared a Dictionary variable called costs. See the MSDN Library for documentation of this standard .NET class. The dictionary variable can store a collection of keys and values. In this example, the key is a string (which we will use to store the report group name) and the value is a List of integers (which we will use to store the remediation costs for all the applications in the report group). See the MSDN Library for documentation of the List class.

As well as the dictionary, we have declared three string variables to store the names of the three report groups that we will create.

Now we are ready to implement the Initialize() function. Here it is:

Public Overrides Sub Initialize()        
   costs.Add(win7group, New List(Of Integer)) 
   costs.Add(appvgroup, New List(Of Integer)) 
   costs.Add(othergroup, New List(Of Integer)) 
End Sub

In this example, we have simply added an item to our costs dictionary variable for each of our three report groups. We have specified the item keys using the three string variables that we declared earlier to store the names of the report groups.

ForwardPath()

The ForwardPath() function is the standard function that all Forward Path scenarios must implement. The ForwardPath() function must have the following signature:

Public Function ForwardPath(ByVal currentApplication _ 
   As Application) As Output 
      ' Enter your code here. 
End Function

In this example we will separate the applications into three report groups – we add applications that have a green Windows 7 RAG status to the "Windows 7" report group, we add applications that have a green App-V RAG status to the "App-V" report group, and we add the rest of the applications to a "Retire" report group. For each application, we will add a random hard-coded number to the dictionary to represent the cost of remediating the application:

Public Function ForwardPath(ByVal currentApplication _ 
   As Application) As Output  
 
   Dim result As New Output() 
 
   ' Is the application green for Windows 7? 
   If (currentApplication.Modules.Windows7.RAG = RAG.Green) _ 
     Then 
      result.Outcome = "Windows 7 OK" 
      result.RAG = RAG.Green 
      result.Cost = 33 
       
      ' Add the application to the "Windows 7" group. 
      result.ReportGroups.Add(win7group) 
 
      ' Add the remediation cost to the dictionary variable. 
      costs.Item(win7group).Add(33) 
   Else 
 
      ' Is the application green for App-V? 
      If (currentApplication.Modules.AppV.RAG = RAG.Green) Then 
         result.Outcome = "App-V OK" 
         result.RAG = RAG.Green 
         result.Cost = 170    
    
         ' Add the application to the "App-V" group. 
         result.ReportGroups.Add(appvgroup) 
 
         ' Add the remediation cost to the dictionary variable. 
         costs.Item(appvgroup).Add(170) 
      Else 
 
         ' The application is not green for Windows 7 or App-V. 
         result.Outcome = "Not suitable for migration." 
         result.RAG = RAG.Red 
         result.Cost = 713 
 
         ' Add the application to the "Retire" group. 
         result.ReportGroups.Add(othergroup) 
 
         ' Add the replacement cost to the dictionary variable. 
         costs.Item(othergroup).Add(713) 
      End If  
   End If 
 
   result.Display.SourcePath.Visible = false 
 
   ForwardPath = result 
 
End Function

Notice that we have used If ... Then ... Else statements to divide the applications into the three report groups based on the RAG status for the Windows 7 and App-V reports. We could have based the report groups on the application group to which the applications belong. A later example in this topic will show you how to do that.

Note: It is possible to add the same application to more than one group. If you do this, when your run the report, the application will appear under each group to which it has been added and its values will be aggregated into each group's totals. This may result in the application's values being added to the report total more than once. Depending on the report, this could potentially be misleading. It is up to you as the script author to ensure that applications are not added to more than one group unless this is your intention.

ProcessGroups()

If it is specified in the scenario, AppDNA calls the ProcessGroups() function for each report group generated by the ForwardPath() function. Typically you use the ProcessGroups() function to define the columns that appear at the group-level in the Forward Path report.

The ProcessGroups() function must have the following signature:

Public Overrides Sub ProcessGroup(group As _ 
   ForwardPathReportGroup) 
      ' Enter your code here.  
End Sub

Notice that the ForwardPathReportGroup object is passed into this function. This represents the current report group. It has two properties: Name, which is a String that stores the group's name, and CustomColumns, which is a dictionary of strings. You use the CustomColumns property to specify the names of the columns that are shown at the group level and what they contain.

Here is an example:

Public Overrides Sub ProcessGroup(group As _ 
   ForwardPathReportGroup) 
 
   ' 1. Define a report group column to show the total  
   ' cost for the applications in the group and format it 
   ' as currency. 
   group.Columns.Item("Cost").Value = _ 
      costs.Item(group.Name).Sum() 
   group.Columns.Item("Cost").Format = "{0:C}" 
   group.Columns.Item("Cost").Culture = "en-US" 
  
   ' 2. Define a report group column to show the number of 
   ' applications in the group. 
   group.Columns.Item("Count").Value = _ 
      costs.Item(group.Name).Count 
 
   ' 3. Define a report group column that has a link to 
   ' further information. 
   group.Columns.Item("Link").Value = _ 
   "<a target=""_blank"" href=""http://www.google.com"">More information</a>" 
 
End Sub

Notice that we have defined three columns:

  1. This defines the Cost column, which shows the total remediation cost of the applications in each group. To get the total cost, we are using the List.Sum() method to aggregate all the cost values stored for the group in the costs dictionary variable. The List class has other aggregation functions that you can use to get the average, minimum, or maximum value (for example) stored for the group. See the MSDN Library for documentation of the List class.

    Notice that we have set the Format property on the column to {0:C} (which specifies that the value is a currency). And we have set the Culture property to a value of en-US to specify that the currency symbol is the US dollar symbol. See the Sort and format Forward Path reports for more on formatting the output.

  2. This defines the Count column, which shows the number of applications in each group. This time we are using the List.Count() method to get the number of items (which represent applications) stored for the group in the costs dictionary variable.
  3. This defines the Link column, which includes HTML code that defines a hard-coded hyperlink. This is an over-simplistic example that demonstrates that you can include HTML code in the column.
Note: AppDNA automatically generates a column that shows the group name. This is called the "Group" column.

GetGroupColumnSummary()

If it is specified in the scenario, AppDNA automatically calls the GetGroupColumnSummary() function for each column defined in the ProcessGroups() function, plus the auto-generated "Group" column (which stores the group name). The GetGroupColumnSummary() function must have the following signature:

Public Overrides Function GetGroupColumnSummary(groupColumnName _ 
   As String) As String		 
      ' Enter your code here. 
End Function

Notice that the name of the column is passed into the function as a String and the function returns a string, which is the text that is inserted in the corresponding column in the report total row.

You use the GetGroupColumnSummary() function to define the report-level values. For example:

Public Overrides Function GetGroupColumnSummary(groupColumnName _ 
   As String) As String	 
 
   If groupColumnName = "Group" Then 
 
      ' Put the text "Total" in the automatically-generated  
      ' "Group" column. 
      GetGroupColumnSummary = "<b>Total</b>" 
 
   Else If groupColumnName = "Cost" Then 
 
      ' Declare a variable to store the total cost. 
      Dim sum As Decimal = 0 
 
      ' Iterate through the costs variable to get the total cost. 
      For Each key As String In costs.Keys 
         sum += costs.Item(key).Sum() 
      Next 
 
      ' Format the total as currency. 
      GetGroupColumnSummary = _ 
         String.Format(New _ 
            System.Globalization.CultureInfo("en-US"), _ 
            "{0:C}", sum) 
 
   Else If groupColumnName = "Count" Then 
 
      ' Declare a variable to store the overall count. 
      Dim count As Integer = 0 
 
      ' Iterate through the costs variable to get the total  
      ' count. 
      For Each key As String In costs.Keys 
         count += costs.Item(key).count() 
      Next 
 
      GetGroupColumnSummary = String.Format(count) 
 
   Else 
 
      ' Leave the "Link" column blank. 
      GetGroupColumnSummary = "" 
 
   End If 
 
End Function

AppDNA calls this function once for each group column we defined in the ProcessGroups() function, plus the auto-generated "Group" column. We therefore use an If ... Then ... Else statement to test which column is being processed. Let's look at what we are doing for each column:

  1. This is the auto-generated Group column. For this column, we are simply generating the text "Total". Notice that we have enclosed the text in <b></b> tags. These are standard HTML tags that specify that the text should be rendered as bold. You can use any HTML code in any of the columns.
  2. This is the Cost column. To get the total for the report, we declare a working variable. Then we iterate through all the items in the global costs dictionary variable and add their total values to the working variable, which we then return.

    The return value is a string. We have used the String.Format() method to format the cost value as a currency and we have used the CultureInfo class to specify the US dollar currency symbol. See the MSDN Library for documentation on formatting strings.

  3. For the Count column, we have used a similar technique to generate a total value for the report.
  4. We have left the Link column blank.

This brings us to the end of the code in the scenario that was used to generate the Forward Path report shown at the beginning of this topic. Later in this topic we will learn how to create report groups based on the AppDNA application group to which the applications already belong.

OnProcessingComplete()

If it is specified in the scenario, AppDNA automatically calls the OnProcessingComplete() once after all the rest of the processing is complete. You can use this function to perform any final processing before the report is displayed.

The OnProcessingComplete() function must have the following signature:

Public Overrides Sub OnProcessingComplete() 
   ' Enter your code here. 
End Sub

Group by the AppDNA application group

In this section, we will change the scenario to group the applications according to the application group to which they already belong. If any application is not a member of a group, it will appear in an Ungrouped category.

Within the scenario, you can find out which group an application belongs to, through the Application.Groups property. Applications can belong to more than one group. Just as noted above, if you add all of the groups to the report, the application will appear under each group that it belongs to. Consequently, depending on how you author the script, there is a possibility that application remediation costs can be added into the report total more than once.

These functions contain detailed comments that explain how they work.

Public Function ForwardPath(ByVal currentApplication _  
   As Application) As Output 
 
   Dim result As New Output() 
 
   If (currentApplication.Modules.Windows7.RAG = RAG.Green) Then 
      result.Outcome = "Windows 7 OK" 
      result.RAG = RAG.Green 
   Else 
      ' If the RAG for Windows 7 is not green, 
      ' check if it's green for App-V 
      If (currentApplication.Modules.AppV.RAG = RAG.Green) Then 
         result.Outcome = "App-V OK" 
         result.RAG = RAG.Green 
      Else 
         result.Outcome = "Not suitable for migration." 
         result.RAG = RAG.Red 
      End If 
    End If 
 
   ' We're temporarily using a hard-coded cost. 
   result.Cost = 713 
 
   ' Iterate through the groups to which the application  
   ' belongs. 
   For Each group As String In currentApplication.Groups 
 
      ' Add the application to the corresponding report group. 
      result.ReportGroups.Add(group) 
 
      ' Check whether the group has already been added to our 
      ' global dictionary variable. 
      If costs.ContainsKey(group) Then 
 
         ' Add the application's cost to the global variable. 
         costs.Item(group).Add(713) 
      Else 
 
         ' Add the group to the global variable and then 
         ' add the application's cost. 
         costs.Add(group, New List(Of Integer)) 
         costs.Item(group).Add(713) 
 
      End If 
   Next 
 
   ForwardPath = result 
 
End Function 
 
Public Overrides Sub ProcessGroup(group As ForwardPathReportGroup) 
 
   If costs.ContainsKey(group.Name) Then 
      group.Columns.Item("Count").Value = _ 
         costs.Item(group.Name).Count 
      group.Columns.Item("Cost").Value = _ 
         costs.Item(group.Name).Sum() 
      group.Columns.Item("Cost").Format = "{0,-10:C}" 
      group.Columns.Item("Cost").Culture = "en-US" 
      group.Columns.Item("Link").Value = _ 
      "<a href=""http://www.google.com"">More information</a>" 
   End If 
 
End Sub

Hide the display of groups

Sometimes you may want to display a grouped report without the grouping. You can do this by using the ForwardPathReportSettings.DisplayGroups property. This property is automatically set to true when you include group handling code in your scenario. However, you can explicitly set it to false in your scenario to hide the display of the groups in the report.

The Settings object (which is of type ForwardPathReportSettings) is available throughout the scenario. Typically you set the property in the Initialize() function, like this:

Settings.DisplayGroups = false

To show the group again, simply comment this line out.