TQL in tracker reports
You can use TQL syntax in tracker reports “Expert” mode in the search area. Saving a TQL query works the same way as saving a “Normal” mode report.
The goal of TQL is to help you filter very precisely which artifacts you want to see in a tracker report. To achieve this filtering, you can build a query with one or several conditions that artifacts must match in order to be selected. Once selected, artifacts are displayed and you can select which columns you want to see and how to order artifacts with the web user interface, the same way as “Normal” mode reports.
Queries
Note
SELECT
, FROM
and ORDER BY
syntax is not supported in Tracker reports. Please refer to the TQL Limitations table for details about the syntax differences.
Comparison operators
For string, text, files, and @comments:
=
,!=
For date, integer and float fields:
=
,!=
,<
,<=
,>
,>=
,BETWEEN()
For list fields:
=
,!=
,IN()
,NOT IN()
Comparison values
For string, text, and files fields:
string
, for example:'text between simple quotes'
or"text between double quotes"
For integer fields:
integer
(3
) orstring
convertible to integer (for example:"3"
)For float fields:
integer
(3
),float
(0.5
) orstring
convertible to float (for example:'3.6'
)For date fields:
NOW()
orstring
convertible to date (for example:"2024-10-07"
)For list fields: matching list values (for example:
"In Review"
,"Ongoing"
)For list fields bound to users:
MYSELF()
orstring
user names (for example:"jdoe"
,"John Doe"
)For list fields bound to user groups:
string
matching either the name of a user-defined (“Static”) user group (for example:"Customers"
) or matching the translated system-defined (“Dynamic”) user group name (for example:"Project members"
).For @comments:
string
Empty string ''
can be used for any field to specify no value.
Dynamic value for date fields: NOW()
start_date > NOW()
matches all artifacts where the fieldstart_date
is greater (more recent) than the current time (time when the query is displayed).You can use interval periods with
NOW()
, for examplesubmitted_on > NOW() - 1m
will matches all artifacts that have been created during the last month. The supported specificators are:years (
y
)months (
m
)weeks (
w
)days (
d
)
Dynamic value for list fields bound to users: MYSELF()
owner = MYSELF()
matches all artifacts where the field owner
is equal to the current user.
Search in comments
@comments = 'Lorem ipsum'
matches all artifacts where at least one follow-up comment contains the stringlorem ipsum
. The comparison is case-insensitive.@comments = ''
returns the list of artifacts without any comments@comments != ''
returns the list of artifacts with at least one comment
When searching in comments, you should be aware of some limitations:
Searches are done for words longer than 3 characters
Some words are not taken into account because they are too common (like
the
,a
, …)
Search in files
attachment = 'minutes'
matches all artifacts where there is at least one attached file with the filename containing “minutes” (for example:"Minutes-20180101.docx"
) or the description containing “minutes” (for example:"Minutes of last meeting"
). The comparison is case-insensitive.
attachment != 'minutes'
matches all artifacts where there isn’t any attached files with filename or description containingminutes
.
attachment = ''
matches all artifacts without any attached files
attachment != ''
matches all artifacts that have at least one attached file
Search for linked artifacts
Generic syntax
Artifact links make an oriented graph: you have to specify in which direction the links should be matched with TO
or FROM
:
Forward links
IS LINKED TO
matches all artifacts that have a link to another artifactIS NOT LINKED TO
matches all artifacts that don’t have a link to another artifact
Reverse links
IS LINKED FROM
matches all artifacts that are linked to by another artifactIS NOT LINKED FROM
matches all artifacts that are not linked to by another artifact
Link type
The query can be refined by specifying the type of the link: WITH TYPE 'my_type'
.
For example if you want to target all “fixed in” requests: IS LINKED TO WITH TYPE 'fixed_in'
.
Origin & destination
Origin or destination can also be specified: to match an artifact ARTIFACT = 123
, a tracker TRACKER = 'epic'
, or a different tracker TRACKER != 'epic'
.
IS LINKED FROM ARTIFACT = 123
matches all artifacts that are linked to by the artifact #123.IS LINKED TO TRACKER = 'release' WITH TYPE 'fixed_in'
matches all artifacts that are “fixed in” an artifact ofrelease
tracker.IS LINKED TO TRACKER != 'release' WITH TYPE 'fixed_in'
matches all artifacts that are “fixed in” an artifact other thanrelease
tracker.
Query aliases
Some builtin types are not easy to express/understand (hello _is_child
👋) so TQL offers some aliases for better expressiveness of the query.
Search for parent relationship
WITH PARENT
matches all artifacts that have a parent artifact (alias ofIS LINKED FROM WITH TYPE '_is_child'
)WITHOUT PARENT
matches all artifacts that don’t have a parent artifact (alias ofIS NOT LINKED FROM WITH TYPE '_is_child'
)WITH PARENT ARTIFACT = 123
matches all artifacts that have artifact #123 as a parent (alias ofIS LINKED FROM ARTIFACT = 123 WITH TYPE '_is_child'
)WITHOUT PARENT ARTIFACT = 123
matches all artifacts that don’t have artifact #123 as a parent (alias ofIS NOT LINKED FROM ARTIFACT = 123 WITH TYPE '_is_child'
)WITH PARENT TRACKER = 'epic'
matches all artifacts that have an artifact from trackerepic
(regardless of the project) as a parent (alias ofIS LINKED FROM TRACKER = "epic" WITH TYPE '_is_child'
)WITHOUT PARENT TRACKER = 'epic'
matches all artifacts that don’t have an artifact from trackerepic
(regardless of the project) as a parent (alias ofIS NOT LINKED FROM TRACKER = "epic" WITH TYPE '_is_child'
)
Search for children relationship
WITH CHILDREN
matches all artifacts that have a least one child artifact (alias ofIS LINKED TO WITH TYPE '_is_child'
)WITHOUT CHILDREN
matches all artifacts that don’t have a child artifact (alias ofIS NOT LINKED TO WITH TYPE '_is_child'
)WITH CHILDREN ARTIFACT = 123
matches all artifacts that have artifact #123 as a child (alias ofIS LINKED TO ARTIFACT = 123 WITH TYPE '_is_child'
)WITHOUT CHILDREN ARTIFACT = 123
matches all artifacts that don’t have artifact #123 as a child (alias ofIS NOT LINKED TO ARTIFACT = 123 WITH TYPE '_is_child'
)WITH CHILDREN TRACKER = 'task'
matches all artifacts that have an artifact from trackertask
(regardless of the project) as a child (alias ofIS LINKED TO TRACKER = "epic" WITH TYPE '_is_child'
)WITHOUT CHILDREN TRACKER = 'task'
matches all artifacts that don’t have an artifact from trackertask
(regardless of the project) as a child (alias ofIS NOT LINKED TO TRACKER = "epic" WITH TYPE '_is_child'
)
Note
You can use both CHILD
and CHILDREN
in queries to better suit your taste:
WITH CHILDREN ARTIFACT = 123
is the same than WITH CHILD ARTIFACT = 123
Search for test coverage
Looking for test coverage is another common use case that benefits of a dedicated syntax:
IS COVERED
matches all artifacts (eg: user stories) that are covered by tests (alias ofIS LINKED TO WITH TYPE '_covered_by'
)IS COVERED BY artifact = 123
matches all artifacts (eg: user stories) that covered by test 123 (alias ofIS LINKED TO ARTIFACT = 123 WITH TYPE '_covered_by'
)IS NOT COVERED
matches all artifacts (eg: user stories) that are not covered by any test (alias ofIS NOT LINKED TO WITH TYPE '_covered_by'
)IS COVERING
matches all artifacts (eg: tests) that are covering other artifacts (eg: user stories) (alias ofIS LINKED FROM WITH TYPE '_covered_by'
)IS COVERING artifact = 123
matches all artifacts (eg: tests) that are covering artifact (eg: user story) 123 (alias ofIS LINKED FROM ARTIFACT = 123 WITH TYPE '_covered_by'
)IS NOT COVERING
matches all artifacts (eg: tests) that doesn’t cover any other artifacts (eg: user stories) (alias ofIS NOT LINKED FROM WITH TYPE '_covered_by'
)
Query construction
You can assemble your different comparisons with logical operators AND
and OR
and use parenthesis ()
to force precedence.
Query example:
(summary = "soap" OR summary = "rest")
AND description = "documentation" AND story_points BETWEEN(3, 8)
Note
Be careful, you must use the name of fields and not the label to construct queries.
Sending the query to the server can produce the following errors:
The query syntax is incorrect (for example: if you forget a closing quote
"
)The name used in a comparison doesn’t match any existing field name (or there is a mistake in the name)
The value is not defined for the list field (for example:
assigned_to = "non_existent_user"
)The dynamic value is not supported for this field (for example:
text_field = NOW()
)The comparison operator is not supported for this field (for example:
list_field >= 3
)The empty value is not allowed for this comparison (for example:
date_field BETWEEN("", "2017-01-18")
)The query uses
MYSELF()
and the current user is not logged in (for example: when browsing a Tuleap platform as an anonymous user)The field type is unsupported
The query is too complex
Important
The query is too complex when it exceeds a limit. This limit is defined by Site Administrators on Site Administration > Tracker > Report.
Pro tips
For a better usability in building query, Tuleap provides syntax highlighting
and auto-completion (ctrl+space
on field names).
Moreover, in order to help you find out which fields you can use for comparisons, there is a select box with all allowed fields. If you click on one of them, the field’s name is added to the query.
Date operators
When searching on date fields using TQL, you should be aware of the sometimes subtle differences between the various operators. We have found this diagram helpful. Here, we use all available operators supporting date fields and a given date (“2017-01-18”) and represent the period that will be selected as a result. For example, date_field > "2017-01-18"
means date_field
is later than 2017-01-18 23:59:59, while date_field >= "2017-01-18"
means date_field
is later than or equal to 2017-01-18 00:00:00