CQL Escalation Services

There is a specialized case of a job, where the results of a CQL query are passed to the code you provide. It saves you having to write the code to execute the query, and set the impersonation context.

In the script binding a variable hits is available, which is an Iterable<ContentEntityObject>. Therefore you can do something with each piece of content returned using:

hits.each { content ->
    // do something with content

Depending on your query, the type of content in the above example will be either a Page, an Attachment, a BlogPost, or a Comment etc.

However, it will always be a ContentEntityObject.

If you only want to do something with pages, then use a query that only selects pages, eg space = ds and type = page.

Alternatively, you can use instanceof in your script to do different things depending on the type of content.

You should specify a user that has permission to see the content that you want.

Your service code is automatically wrapped into a transaction template, but you should take care your service doesn’t not run for very long, unless it’s at a quiet time, e.g. the weekend.


Add Label to Outdated Pages

As a worked example, we’ll add a label to pages that haven’t been modified for one month in a particular space. You may do this if you need to ensure that content is kept current, although this solution is deliberately over-simplistic.

The query here selects pages (and only pages, not attachments, blog posts, comments etc) in the demonstration space that have not been modified for over 4 weeks. Look at the examples and refer to the CQL documentation if you need help.

Note that the number of hits from the query is shown. You should be careful that you don’t inadvertently select more content than you intended to. The user to run the query as is also shown highlighted.

Clicking Run now will run the service right now, so you can test (on a development instance ideally).

The content of the file in the screenshot is below - you can either copy it as an inline script, or put it on a file under a script root.

import com.atlassian.confluence.labels.Label
import com.atlassian.confluence.labels.LabelManager
import com.atlassian.confluence.labels.Namespace
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.scheduler.JobRunnerResponse

def labelManager = ComponentLocator.getComponent(LabelManager)
hits.each { page -> // <1>

    def labelName = "requires-review"

    def label = labelManager.getLabel(labelName)
    if (!label) {
        label = labelManager.createLabel(new Label(labelName, Namespace.GLOBAL))

    if (!page.labels*.name.contains(labelName)) {
        log.info("Add label to page: ${page.title}")
        labelManager.addLabel(page, label) // <2>

return JobRunnerResponse.success()

Line 8: Iterate over the items returned from the CQL

Line 19: Add label to the page

Add Comment to Outdated Pages

Alternatively you may wish to add a comment to any page that has not been modified for over three months, and has had no comment in over three months. This is very similar to the example above so refer to that for any missing information.

The query would be:

space = ds and type = page and lastModified < now("-14w")

CQL does not support searching on months or years, so multiply the weeks value appropriately.

The code is:

import com.atlassian.confluence.pages.CommentManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.scheduler.JobRunnerResponse

def commentManager = ComponentLocator.getComponent(CommentManager)

hits.each { page ->

    if (!commentManager.getPageComments(page.id, new Date() - 98)) { // <1>
        log.debug("Adding 'old page' comment to page: ${page.title}")
        commentManager.addCommentToObject(page, null, "Old page! Please review")

return JobRunnerResponse.success()

Line 9: check we don’t have a comment added within the last three months (in days)

On this page