Task Report Tutorial

The walk-through on this page will help you create your first task report. Task reports display task information with a link for users to open each task and begin working on it.

When you save a Tempo report as a task report, it appears on the Tasks tab. Task reports are also used with sites.

For our example, we’ll pull data from the My Tasks process report. We’ll describe how to use the a!queryProcessAnalytics() system function to display data from the process report in a SAIL grid. Then, we’ll show you how to create links to tasks and format the data so it looks more user-friendly. Finally, we’ll show you how to add dynamic filters to your report.

Use the data provided to understand how the configurations work. Then, try it with your own data.

The content below assumes a basic familiarity with SAIL interfaces, specifically the Paging Grid and Dropdown components, and focuses more on the specifics of designing and creating a task report. Consider going through the SAIL and Grid Tutorials first and taking a look at the Tempo Report Design page before proceeding.

Create the Appian Tutorial Application

The Appian Tutorial application is used to contain the design objects created while working through this tutorial.

The tutorial application only needs to be created once. If you have already created the tutorial application, skip the steps below.

To create the Appian Tutorial application

  1. Log in to the Appian Designer environment (for example, myappiansite.com/suite/design).
  2. Click New Application.
  3. In the Name field, type Appian Tutorial.
  4. Optionally, in the Description field, add a short description.
  5. Click Create.

The application contents view displays. Right now the application is empty. Each design object that you create during the course of this tutorial will appear in this list and be associated with the tutorial application.

Create a Constant for the My Tasks Process Report

Before we create the task report interface, we need to create a constant for the My Tasks process report. This constant will be used in the interface that we will create in the next step.

  1. Navigate to the application contents view of the Appian Tutorial application (if needed).
  2. From the New menu, click Constant. This will open the Create Constant form.
  3. Leave Create from Scratch selected.
  4. Enter MY_TASKS_REPORT in the Name field.
  5. Select Document from the Data Type dropdown.
  6. Enter My Tasks in the Value field and select the document that is suggested.
  7. Enter Examples in the Save In field and select the folder that is suggested.
  8. Click Create.

Create an Interface for the Task Report

Now we will create an interface to be used to display the task report data in a grid. We will use a!queryProcessAnalytics() system function to populate a Paging Grid component with data from the My Tasks process report and create a Tempo task report out of it.

Note that while we are using a built-in report for this example, any process report can potentially be queried.

  1. Navigate to the application contents view of the Appian Tutorial application (if needed).
  2. From the New menu, click Interface. This will open the Create Interface form.
  3. Leave Create from Scratch selected
  4. Enter AT_myTasksReport in the Name field.
  5. Enter Examples in the Save In field and select the folder that is suggested.
  6. Click Create & Edit.

The newly created interface opens in the interface designer. By default, the interface designer opens in a new tab. If you don't see a new tab, check your browser to see if you have pop-ups enabled.

Switch to the Expression View by clicking the pencil button at the bottom of the left-hand pane, as shown below:

image:interface_designer_expression_view_toggle.png

Now enter the following expression:

with(
  local!report: a!queryProcessAnalytics(
    report: cons!MY_TASKS_REPORT
  ),
  a!textField(readOnly: true, value: local!report)
)

The interface's text field will show the text representation of the process report data. You will see something similar to the following:

[
  startIndex=1,
  batchSize=25,
  sort=[field=c2, ascending=false],
  totalCount=66,
  data=
    [c5:0,c4:1,c3:New PR: Purchase Request Number 68,c8:[Group:6]; [Group:7]; [Group:3],c7:,c2:12/19/2014 2:23 PM GMT+00:00,c0:Review Purchase Request: Purchase Request Number 68,dp0:268440077,dp2:,dp4:,dp3:268435772,dp5:268435772,dp7:,dp8:];
    [c5:0,c4:1,c3:New PR: Purchase Request Number 43,c8:[Group:6],c7:,c2:12/19/2014 2:23 PM GMT+00:00,c0:Approve Purchase Request: Purchase Request Number 43,dp0:268440067,dp2:,dp4:,dp3:268435771,dp5:268435771,dp7:,dp8:],
  identifiers=268440077; 268440067
  name=My Tasks
  description=A list of all tasks for the current user.
  columnConfigs=
    [label:Name,field:c0,drilldownField:dp0,configuredFormatting:NORMAL_TEXT,configuredDrilldown:TASK_DETAILS];
    [label:Received,field:c2,drilldownField:dp2,configuredFormatting:DATE_TIME,configuredDrilldown:];
    [label:Priority,field:c4,drilldownField:dp4,configuredFormatting:PRIORITY_ICON,configuredDrilldown:];
    [label:Process,field:c3,drilldownField:dp3,configuredFormatting:NORMAL_TEXT,configuredDrilldown:PROCESS_DASHBOARD];
    [label:Status,field:c5,drilldownField:dp5,configuredFormatting:TASK_STATUS,configuredDrilldown:PROCESS_DETAILS];
    [label:Deadline,field:c7,drilldownField:dp7,configuredFormatting:DATE_TIME,configuredDrilldown:];
    [label:Assigned To,field:c8,drilldownField:dp8,configuredFormatting:USER_OR_GROUP_NAME,configuredDrilldown:],
  errorMessage=
]

Notice that rows are returned in the data array with each column's cell being represented by a key/value pair. Details about each column, including the key to access that column's data from the data array, are provided in the columnConfigs field.

Let's try displaying the data using a grid instead of a single text field. For this example, we’ll take the columns labeled Name, Process, and Status:

load(
  local!pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 5),
  with(
    local!report: a!queryProcessAnalytics(
      report: cons!MY_TASKS_REPORT,
      query: a!query(pagingInfo: local!pagingInfo)
    ),
    a!gridField(
      label: local!report.name,
      instructions: local!report.description,
      totalCount: local!report.totalCount,
      columns: {
        a!gridTextColumn(
          label: local!report.columnConfigs[1].label,
          field: local!report.columnConfigs[1].field,
          data: index(local!report.data, "c0", {})
        ),
        a!gridTextColumn(
          label: local!report.columnConfigs[4].label,
          field: local!report.columnConfigs[4].field,
          data: index(local!report.data, "c3", {})
        ),
        a!gridTextColumn(
          label: local!report.columnConfigs[5].label,
          field: local!report.columnConfigs[5].field,
          data: index(local!report.data, "c5", {})
        )
      },
      value: local!pagingInfo,
      saveInto: local!pagingInfo
    )
  )
)

You should see an interface similar to the following (it will vary based on your personal task list):

image:Task_grid_no_formatting.jpg

Before moving on, however, let's save the interface by clicking Save. Keep this window open so you can quickly modify the interface as we continue.

Create and View the Task Report

Now let's save the interface as a task report and view it in Tempo.

  1. From the settings menu (gear icon), click Save as.... This will display the Save Interface As form.
  2. Enter My Tasks in the Report Name field.
  3. Select the Save as Task Report checkbox.
  4. Confirm that the Application field has the value Application Tutorial in it.
  5. Click Save.

Now that we've created the report, let's navigate to the Tasks tab in Tempo.

image:access-tempo-from-interface-designer.png

You'll now see a link for the My Tasks report below the default filters.

image:task-report-in-tempo.gif

Display Report Columns Dynamically

So far we have created a hard coded report that uses specific numerical indexes (1, 4, 5) and specific keys to access the data (c0, c3, c5). While this works, it's dangerous to rely on hand-entered indexes like this since these might change if someone modifies the report in the future. Since we have the column information available, a much more reliable way to create our grid is to dynamically create grid columns based on the report columns. Doing this means changes to the report will be reflected in our interface without it having to be modified.

In the AT_myTasksReport interface, try our new approach to producing the grid (the gray text indicates what was a part of the previous expression so you can easily see what we added):

load(
  local!pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 5),
  with(
    local!report: a!queryProcessAnalytics(
      report: cons!MY_TASKS_REPORT,
      query: a!query(pagingInfo: local!pagingInfo)
    ),
    a!gridField(
      label: local!report.name,
      instructions: local!report.description,
      totalCount: local!report.totalCount,
      columns: {
        a!foreach(
          items: local!report.columnConfigs,
          expression: a!gridTextColumn(
            label: fv!item.label,
            field: fv!item.field,
            data: index(local!report.data, fv!item.field, {})
          )
        )
        
      },
      value: local!pagingInfo,
      saveInto: local!pagingInfo
    )
  )
)

Even though we did not directly configure any column individually, the a!forEach() function generates a text column for each of the report's configured columns. You should see a grid with the following columns:

  • Name
  • Received
  • Priority
  • Process
  • Status
  • Deadline
  • Assigned To

See also: Looping Functions

However, if you have any tasks that have multiple assignees, you may get an error similar to the following:

Expression evaluation error at function a!gridField [line 4]: A grid component [label=“My Tasks”] has an invalid value for “columns” and “value”. All “data” arrays must not contain more items than the specified “batchSize”, but “batchSize” was 5 and the largest column data array had 11 items.

Multi-valued cells require some special handling that we'll discuss in the Display Tasks with Multiple Assignees section later in this tutorial.

Display Tasks with Multiple Assignees

To explain why multi-valued cells cause an error in our simple design, we will have to quickly review how nested arrays behave in Appian expression language. We are accessing the data via the following expression: index(ri!data, ri!columnConfig.field, {})

Suppose there are three cells in this column that contain 11, the array 22 and 23, and 33. We might expect this index function to return: { 11, { 22, 23 }, 33 } However, in most cases when arrays are nested, one single array is created using the elements the nested arrays. So { 11, { 22, 23 }, 33 } becomes { 11, 22, 23, 33 }. You can try this yourself by testing an expression rule with the definition length({ 11, { 22, 23 }, 33 }).

Given this behavior, how do we keep multiple values in a cell? One solution is to use looping functions, taking advantage of the fact they can operate on nested arrays. We simply need an expression that converts the contents of a data cell to text using the joinarray() function. Now we can update the AT_myTasksReport interface columns expression to:


a!foreach(
  items: local!report.columnConfigs,
  expression: with(
    local!columnData: index(local!report.data, fv!item.field, {}),
    local!columnDataCount: count(local!columnData),
    a!gridTextColumn(
      label: fv!item.label,
      field: fv!item.field,
      data: if(
        local!columnDataCount > 0,
        a!forEach(
          items: local!columnData,
          expression: joinarray({fv!item}, ", ")
        ),
        {}
      )
    )
  )
)      

This change corrects the issue that caused the error. Notice that multi-valued cells no longer cause an error in the grid. (Note that the report will continue to work as it did before if your task list had no multi-value cells it.)

Unlike the process report, the grid we just created only displays the task name. Users have no way to open the task. What we want to do is link the Name column to the task, but the other columns should remain unlinked. To put it another way, we want to respect the task drilldown configuration on the Name column.

We can do this by updating the dynamic column expression with the following definition:

a!foreach(
  items: local!report.columnConfigs,
  expression: with(
    local!columnData: index(local!report.data, fv!item.field, {}),
    local!columnDataCount: count(local!columnData),
    a!gridTextColumn(
      label: fv!item.label,
      field: fv!item.field,
      data: if(
        local!columnDataCount > 0,
        a!forEach(
          items: local!columnData,
          expression: joinarray({fv!item}, ", ")
        ),
        {}
      ),
      links:if(
       fv!item.configuredDrilldown = "TASK_DETAILS",
       a!forEach(
        items: index(local!report.data, fv!item.drilldownField, {}),
        expression: a!processTaskLink(task: fv!item)
       ),
       null
      )
    )
  )
)  

Now if you return to your AT_myTasksReport interface, the tasks should be linked. We are now using a!processTaskLink() to create a link for each data point if the column is configured with a "Task Details" drilldown. We are accessing the result of the column's drilldown expression, =tp!id (the task id), from the data row using the drilldownField from the column configuration.

See also: Process Task Link

Format the Status Column

You may have noticed that the Status column returned numbers instead of the text you normally see in the Portal task report. This is because process reports auto-format some columns, including task status, task priority, users, dates, and more, but we are telling our SAIL grid to display every column as text.

We can apply one or more of these formats by using a!forEach() to call a formatting expression if the report column is configured with a relevant formatting type.

This expression uses the task status number as an index into a set of status names. Note that because each data row is a Dictionary that maps keys to values of Any Type, tointeger must be used to convert the cell value from Any Type to Number (Integer).

Now we can update the AT_myTasksReport interface rule with the following definition:

with(
  local!columnData: index(ri!data, ri!columnConfig.field, {}),
  local!columnDataCount: count(local!columnData),
  a!gridTextColumn(
    label: ri!columnConfig.label,
    field: ri!columnConfig.field,
    data: if(
      local!columnDataCount > 0,
      a!forEach(
          items: local!columnData,
          expression: if(
            fv!item = "TASK_STATUS",
            index(
              {
                "Assigned",
                "Accepted",
                "Completed",
                "Not Started",
                "Cancelled",
                "Paused",
                "Unattended",
                "Aborted",
                "Cancelled By Exception",
                "Submitted",
                "Running",
                "Error"
              },
              /*
              * Task status ids start with 0, so add one to reach the first index 
              */
              tointeger(index(local!columnData, fv!item + 1, -1 )),
              "Other"
            ),
            joinarray({fv!item}, ", ")
          )
        ),
        
      {}
    ),
    links:if(
      fv!item.configuredDrilldown = "TASK_DETAILS",
      a!forEach(
        items: index(local!report.data, fv!item.drilldownField, {}),
        expression: a!processTaskLink(task: fv!item)
      ),
      null
    )
   )
  )
)

Now instead of numbers in the status column, there are words like Assigned and Completed. For an example that uses an image column to display an icon for process statuses, see the Display Processes by Process Model with Status Icons SAIL recipe.

Add a Predefined Filter

With process reports, Appian lets users add their own filters to those configured in the report. For task reports, we can add extra filters via the query parameter of a!queryProcessAnalytics(). Then we can connect them to another SAIL component so users can use that component to change the filter on the report.

For our example, let’s add a Dropdown component so users can filter the tasks by the most common statuses.

Modify the AT_myTasksReport interface with the following:

=load(
  local!pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 5),
  local!statusFilter: 0,
  with(
    local!report: a!queryProcessAnalytics(
      report: cons!MY_TASKS_REPORT,
      query: a!query(pagingInfo: local!pagingInfo)
    ),
    {
      a!dropdownField(
        label: "Status",
        choiceLabels: { "Assigned", "Accepted", "Completed", "Not Started" },
        choiceValues: enumerate(4),
        value: local!statusFilter,
        saveInto: local!statusFilter
      ),
      a!gridField(
        label: local!report.name,
        instructions: local!report.description,
        totalCount: local!report.totalCount,
        columns:{
          a!foreach(
            items: local!report.columnConfigs,
            expression: with(
              local!columnData: index(local!report.data, fv!item.field, {}),
              local!columnDataCount: count(local!columnData),
              a!gridTextColumn(
                label: fv!item.label,
                field: fv!item.field,
                data: if(
                  local!columnDataCount > 0,
                  a!forEach(
                    items: local!columnData,
                    expression: if(
                      fv!item = "TASK_STATUS",
                      index(
                        {
                          "Assigned",
                          "Accepted",
                          "Completed",
                          "Not Started",
                          "Cancelled",
                          "Paused",
                          "Unattended",
                          "Aborted",
                          "Cancelled By Exception",
                          "Submitted",
                          "Running",
                          "Error"
                        },
                        /*
                        * Task status ids start with 0, so add one to reach the first index 
                        */
                        tointeger(index(local!columnData, fv!item + 1, -1 )),
                        "Other"
                      ),
                      joinarray({fv!item}, ", ")
                    )
                  ),
                  {}
                ),
                links:if(
                  fv!item.configuredDrilldown = "TASK_DETAILS",
                  a!forEach(
                    items: index(local!report.data, fv!item.drilldownField, {}),
                    expression: a!processTaskLink(task: fv!item)
                  ),
                  null
                 )
               )
            )
          )
        },
        value: local!pagingInfo,
        saveInto: local!pagingInfo
      )
    }
  )
)

In this expression, we pass a filter to a!queryProcessAnalytics() that uses a statusFilter local variable as its value. That variable is set by the Dropdown component, where the task statuses are passed as choiceLabels and the corresponding numeric values are passed as choiceValues. Because the numeric values of these specific statuses are 0, 1, 2, and 3, we can use the enumerate() function to return those values. Since we set the default value of the filter to 0 which corresponds to the value of Assigned, only the tasks with a status of Assigned are returned originally. If you select a different value from the dropdown, the grid updates to display only tasks that correspond to the selected status.

You may have seen an error if you were on any page other than the first page of the grid when you selected a new filter value. This is because the start index of the paging configuration persisted across reevaluations may not apply when you select a new value. If you are on the second page of the grid (for example when local!pagingInfo.startIndex is 6) when you change the filter, the start index that will be applied might be greater than the total number of tasks that can be returned for that filter, which causes an error. To avoid this, we need to reset the start index each time a new filter is selected.

To account for this, modify the AT_myTasksReport interface with the following:

a!dropdownField(
  label: "Status",
  choiceLabels: { "Assigned", "Accepted", "Completed", "Not Started" },
  choiceValues: enumerate(4),
  value: local!statusFilter,
  saveInto: {
    local!statusFilter,
    a!save(local!pagingInfo.startIndex, 1)
  }
)

The interface should look the same, but now when the filter is changed the startIndex is set back to 1. By updating only the startIndex with a!save(), the rest of the paging configuration, including any sorting the user applied, is preserved.

Click Save. Now go back to Tempo, and see that the filtered task list interface is now available.

FEEDBACK