How do I list pages that contain specific macros in a specific space

Stan Ry
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 14, 2019

There's a wonderful thread over here (How can i find pages on which a macro is used, programmatically?) that discusses analysis of Confluence instance and explains how to find all pages that use macros installed on the instance. 

This involves using adminstration console in Confluence in order to enumerate all macros registered in the instance.

However, it doesn't deep further into details on listing all pages that use specific macros in a specific Confluence. For example, I don't have administrative access to Confluence administration facilities, and control over my space is all I have.

I would like to run a kind of editorial analysis against the space in order to find out on what pages in the space specific macros are used.

I managed to mofidy the original code provided by @Davin Studer to work for a specific space name and for a single macro name. But I can't figure out how to modify the script to work on a collecton of macros.

Since I don't have administrative access to adminstration facilities, the admin console, whatsoever, I am using the modified code by pasting it inside the Markdown plugin that I insert on a Confluence page.

Here's the modified script:

<script type="text/javascript">

//The script only works on fields with a single value...
var macroObjects = {
'name': ['info'],
'title' : ['Info']
}

///////////////////////////////////////////////////////
// Can't manage to work it with multiple macro names
///////////////////////////////////////////////////////

/* var macroObjects = [
{
'name': ['info'],
'title' : ['Info']
},

{
'name': ['ui-text-box'],
'title': ['UI Text Box']
}
]; */

var queue = {
macros: [macroObjects],
current: 0,
start: function () {
this.macros.sort(compare);
this.current = 0;
this.next();
},
next: function () {
if (this.current >= this.macros.length) {
AJS.$('#queryStatus span').removeClass('aui-lozenge-current');
AJS.$('#queryStatus span').addClass('aui-lozenge-success');
AJS.$('#queryStatus span').text('Report Complete');
AJS.tablessortable.setTableSortable(AJS.$('#macroUsageReport table'));
return null;
}
else {
this.current += 1;
lookupMacro(this.macros[this.current - 1]);

}
}
};

function lookupMacro(macro) {
var html = '';
var searchURL = '';
AJS.$('#queryStatus span').text('Getting counts for macro: ' + macro.title);
searchURL = '/wiki/rest/searchv3/1.0/search?where=my_space_name&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name);
AJS.$.ajax( {
type: 'GET',
url: searchURL,
dataType: "json",
success: function(data) {
if(data.total > 0) {
html = '<tr>';
html += ' <td class="confluenceTd">' + macro.title + '</td>';
html += ' <td class="confluenceTd">' + macro.name + '</td>';
html += ' <td class="confluenceTd">' + data.total + '</td>';
html += ' <td class="confluenceTd"><a href="/wiki/dosearchsite.action?where=my_space_name&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name) + '" target="_blank">Show all pages</a></td>';
html += '</tr>';
}
AJS.$('#macroUsageReport table tbody').append(html);
queue.next();
},
error: function (x, y, z) {
AJS.$('#queryStatus span').removeClass('aui-lozenge-current');
AJS.$('#queryStatus span').addClass('aui-lozenge-error');
AJS.$('#queryStatus span').text('Error getting counts for macro:' + macro.title);
}
} );
}

function compare(a, b) {
if (a.title < b.title) {
return -1;
}
if (a.title > b.title) {
return 1;
}
return 0;
}

AJS.toInit(function() {
queue.start();
} );

</script>
<div id="queryStatus">
<span class="status-macro aui-lozenge aui-lozenge-current">
</span>
</div>

<div id="macroUsageReport">
<table class="confluenceTable">
<thead>
<tr>
<th>Macro title</th>
<th>Macro name</th>
<th>Found times</th>
<th>Source page</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>

The above script only works for a single macro at a time.

If I define the set of macros that I'd like to scan my space for as

var macroObjects = {
'name': ['info'],
'title' : ['Info']
}

The script returns a page with stats for the macro known as info but doesn't work if I define a dictionary with two macros: info and ui-text-box.

var macroObjects = [
{
'name': ['info'],
'title' : ['Info']
},

{
'name': ['ui-text-box'],
'title': ['UI Text Box']
}
];

I am not a developer, just wanting to clean up my space so that it uses only a single type of the admonotion blocks.

I'd apreciate if someone could help me to make this code run for a set of macro names.

Thank you in advance.

1 answer

1 accepted

3 votes
Answer accepted
Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 14, 2019

Something like this should work.

<script type="text/javascript">
var macroObjects = [{title:'confluence.extra.information.info.label', name:'info'},{title:'confluence.extra.information.warning.label', name:'warning'},{title:'confluence.macros.basic.panel.label', name:'panel'}];
var spaceKey = "conf_all"; // use "conf_all" for all spaces or a space key for a specific space

var queue = {
    macros: macroObjects,
    current: 0,
    start: function () {
        this.macros.sort(compare);
        this.current = 0;
        this.next();
    },
    next: function () {
     if (this.current >= this.macros.length) {
            AJS.$('#queryStatus span').removeClass('aui-lozenge-current');
            AJS.$('#queryStatus span').addClass('aui-lozenge-success');
            AJS.$('#queryStatus span').text('Report Complete');
            AJS.tablessortable.setTableSortable(AJS.$('#macroUsageReport table'));
            return null;
  }
  else {
   this.current += 1;
   lookupMacro(this.macros[this.current - 1]);
  }
    }
};

function lookupMacro(macro) {
    var html = '';
    var searchURL = '';
    
    AJS.$('#queryStatus span').text('Getting counts for macro: ' + macro.title);
    
    searchURL = AJS.params.baseUrl + '/rest/searchv3/1.0/search?where=' + spaceKey + '&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name);
    AJS.$.ajax(
        {
            type: 'GET',
            url: searchURL,
            dataType: "json",
            timeout:60000,
            success: function(data) {
                if(data.total > 0) {
                    var spaces = '';
                    for(var i = 0; i < data.results.length; i++)
                    {
                        if(spaces.indexOf(data.results[i].searchResultContainer.name) == -1)
                        {
                            spaces = spaces == '' ? '' : spaces + ', ';
                            spaces = spaces + data.results[i].searchResultContainer.name;
                        }
                    }
                    html = '<tr>';
                    html += '   <td class="confluenceTd">' + macro.title + '</td>';
                    html += '   <td class="confluenceTd">' + macro.name + '</td>';
                    html += '   <td class="confluenceTd">' + data.total + '</td>';
                    html += '   <td class="confluenceTd">' + spaces + '</td>';
                    html += '   <td class="confluenceTd"><a href="' + AJS.params.baseUrl + '/dosearchsite.action?where=conf_all&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name) + '" target="_blank">macro usage</a></td>';
                    html += '</tr>';
                }
                AJS.$('#macroUsageReport table tbody').append(html);
                
                queue.next();
            },
            error: function (x, y, z) {
                AJS.$('#queryStatus span').removeClass('aui-lozenge-current');
                AJS.$('#queryStatus span').addClass('aui-lozenge-error');
                AJS.$('#queryStatus span').text('Error getting counts for macro:' + macro.title);
            }
        }
    );
}

function compare(a, b) {
    if (a.title < b.title) {
        return -1;
    }
    if (a.title > b.title) {
        return 1;
    }
    return 0;
}

AJS.toInit(function(){
    queue.start();    
});
</script>
<div id="queryStatus">
    <span class="status-macro aui-lozenge aui-lozenge-current"></span>
</div>
<div id="macroUsageReport">
    <table class="confluenceTable">
        <thead>
            <tr>
                <th>Macro Title</th>
                <th>Macro Name</th>
                <th>Times Used</th>
                <th>Spaces Used In</th>
                <th>Pages Used On</th>
            </tr>
        </thead>
        <tbody>
        </tbody>
    </table>
</div>
Stan Ry
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 15, 2019

Hi @Davin Studer ,

Thank you for your help. That helped a lot! And thank you to expand its functionality over the old one, as a layman without any experience with JS I learned a ton. Thanks!

So my core errors were:

1. Incorrect notation for the macroObjects dictionary 

2. Unnecessary declaration of macroObjects as an array in the queue variable that led to an embedded array.

Davin, could you please hint where can I get the class names? How would I know that info refers to the 

confluence.extra.information.info.label

class?

I have only found this XML with standard plugins:

https://bitbucket.org/atlassian/confluence-basic-macros/src/master/src/main/resources/atlassian-plugin.xml

Although these ones are not used as an argument in the core and knowing values for name is all I need, it's still good to know. I'd like to expand the list and locate the ui-text-box plugin as well.

Thank you one more time. That was a good learning experience and fantastic help.

Stan Ry
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 15, 2019

@Davin Studer 

Davin, what would be the proper way to extract search results?

In your script you are referring to the search results page like

<a href="' + AJS.params.baseUrl + '/dosearchsite.action?where=conf_all&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name) + '" target="_blank">macro usage</a>

How to extract them?

The REST result for the request

/rest/searchv3/1.0/search?where=' + spaceKey + '&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name);

returns first something results without pagination. Are there any examples on extracting search results into a table on a page?

Thank you.

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 15, 2019

Actually, looking back at my code you don't really need the class name. I grabbed that programmatically in the Velocity script via the original user macro. I'm not really using the class name for anything other than display purposes.

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 15, 2019

Not really sure I understand your second post.

Stan Ry
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 16, 2019

@Davin Studer 

>Actually, looking back at my code you don't really need the class name. I grabbed that >programmatically in the Velocity script via the original user macro. I'm not really using the >class name for anything other than display purposes.

Yes, but just for the sake of 'integrity' with naming, I was curious to know how that'd be possible.

Ah, those statements starting with '#' are Velocity scripts! Where do you run them in Confluence? In the administrative console?

>Not really sure I understand your second post

How to extract search results from the search page?  As I get it, this

<a href="' + AJS.params.baseUrl + '/dosearchsite.action?where' + 'spaceKey' + '&spaceSearch=true&queryString=macroName:' + encodeURIComponent(macro.name) + '" target="_blank">macro usage</a>

produces a link that opens a new window with search results — pages that contain the 'info' macro.

So the question is: how do I extract the links to the pages in the search result and insert into the original confluenceTable?

Thank you for your help.

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 16, 2019

I honestly don't know of an  easy way to find the class names, but it really isn't all that important. You can make it what you want so that you know what it refers to.

Yes, the stuff with the # in front is Velocity. That is the backbone of Confluence User Macros. Those are created in Confluence Administration ... and they only work on the server and data center offerings. Confluence Cloud does not have user macros.

As for getting the results into a table you could try looping through the rest call instead. However, that may not have all the results. I'm not sure if that API will return all the results or if it will paginate them and require you to run it multiple times to get all the results.

Like Stan Ry likes this
Stan Ry
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 20, 2019

@Davin Studer Thank you for your help.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events