Process reports are one of the oldest components in Appian. I hear many developers trying to avoid them and try to replicate existing functionality by storing task and process data in the database.
At the time I started with Appian in 2010, building Appian UI meant creating portal pages and populating them with process reports and handcrafted HTML. While this seems ridiculous when looking back, I had to learn and understand all the quirks and features of process reports.
In this post, I want to share a simple way to implement great search capabilities on process reports.
Search
When you are using process reports for the first time, it probably feels quite strange. To get more comfortable with them, I highly recommend spending a bit of time with the documentation and tutorials. It will require you to adapt your way of problem-solving. But, once understood, process reports can be a great part of your application design.
In this post, I am using a task report. Task reports can show tasks filtered by processes, process models or user. My report shows all tasks for a set of process models I store in a constant.

In the UI, I want to provide a search field that allows the user to search in any of the fields in the grid. My code is this:
a!localVariables(
local!search,
/* Report meta data, just to identify the fields by their C* names */
local!reportMetaData: a!queryProcessAnalytics(
report: cons!SSH_REPORT_TASKS_PER_PROCESS_MODEL,
contextProcessModels: cons!SSH_MODELS_FOR_REPORT,
query: a!query(
pagingInfo: a!pagingInfo(1, 1)
)
),
{
a!textField(
labelPosition: "COLLAPSED",
placeholder: "Search ...",
value: local!search,
saveInto: local!search,
refreshAfter: "KEYPRESS"
),
a!gridField(
label: "Tasks",
data: a!queryProcessAnalytics(
report: cons!SSH_REPORT_TASKS_PER_PROCESS_MODEL,
contextProcessModels: cons!SSH_MODELS_FOR_REPORT,
query: a!query(
pagingInfo: fv!pagingInfo,
logicalExpression: if(
len(local!search) > 2,
a!queryLogicalExpression(
operator: "OR",
filters: {
a!queryFilter(
field: "c0",
operator: "includes",
value: local!search
)
}
),
null
)
)
),
columns: {
a!gridColumn(
label: "Task",
value: fv!row.c0
),
a!gridColumn(
label: "Status",
value: fv!row.c5
),
a!gridColumn(
label: "Assignee",
value: fv!row.c8
),
a!gridColumn(
label: "Owner",
value: fv!row.c7
),
}
)
}
)
It shows like this

And I can search for task names

If you think about adding any process or record related data to task reports, I want you wait a second. As soon as you have more than a single application and users have access to them, they want a “All my Tasks” report. And then, you will struggle to add that related data as each app has a different idea of what that is.
If you still want to do that, I suggest to create a CDT and add a process variable of that type to each process containing tasks. Then, in the process report, add a field that contains a single string made of all the values that you want to use for searching. This can be record titles, customer names, dates, user names etc.
Now, I would like to be able to search for usernames. In my case, this is simple, as the user account name is the email address. I have clients using more cryptic names, and search does not work anymore. The first idea, is to change the report to show the first and last name of the user, collides with the limited capabilities of process reports.
So …
We need to be clever!
Query
We need a way to identify tasks assigned to a user, we search for by first or last name. Our task report can’t do that. But, what if we searched for users first, and then use their account names to look for tasks?
The plugin “Personalization Utilities” provides the function usersearch(). I need to adapt my code a bit.
a!localVariables(
local!search,
/* Report meta data, just to identify the fields by their C* names */
local!reportMetaData: a!queryProcessAnalytics(
report: cons!SSH_REPORT_TASKS_PER_PROCESS_MODEL,
contextProcessModels: cons!SSH_MODELS_FOR_REPORT,
query: a!query(
pagingInfo: a!pagingInfo(1, 1)
)
),
local!foundUsers: if(
len(local!search) > 2,
union(
usersearch({"firstName"}, {0}, {local!search}),
usersearch({"LastName"}, {0}, {local!search}),
),
{}
),
{

Then I add one more filter to my query.
filters: {
a!queryFilter(
field: "c0",
operator: "includes",
value: local!search
),
a!foreach(
items: local!foundUsers,
expression: a!queryFilter(
field: "c7", /* User field, or common search field */
operator: "includes",
value: local!foundUsers
)
),
}
Now, I can easily search for tasks accepted by a user.
If usersearch() returns too many users, you can reduce the set in local!foundUsers by creating the intersection with the group that holds all users having access to your application.
local!foundUsers: if(
len(local!search) > 2,
intersection(
fn!getdistinctusers(cons!SSH_GROUP_APP_USERS),
union(
usersearch({"firstName"}, {0}, {local!search}),
usersearch({"LastName"}, {0}, {local!search}),
)
),
{}
)
Summary
Keep in mind that process reports are tricky. Trying to use this method to search for assignees, will not work. The report internal data type is “List of User or Group”, and the available operators in query filters do not support a list-to-list comparison.
I hope this makes process reports a bit more approachable for you.
Keep on rocking!
