Managing Review Comments
Asked by Stacy Dow:

Hey All,

Still new to authoring in Confluence... The main reason we went to Confluence is because our engineers had a major pain point in providing review comments using Adobe Acrobat Review Server. Their hope is that they can modify small details directly in the source, AND provide comments on pages. (Don't tell me it's a terrible idea, because I lost that battle.)

So, now each page in a doc has a growing list of comments. Tracking this activity, even on a small document, is so flawed! I have no way of marking a comment done, filtering only unresolved comments, seeing a history of added and deleted comments, etc. So, what is already happening is that I'm missing newly added comments/replies to threads. So, I started deleting comments once I incorporated them until I needed to go back and see the comment again and had no way to restore it.

I'm so frustrated. There is no way this is going to work on bigger docs. WAY too error prone...

Does anyone know if there's an add-on? Or a different way to get/track feedback on the actual Confluence page (not a PDF output, since they don't want to review that way). HELP???

TIA!


Answers:
author: Rodney Hughes
body: Firstly it is a brilliant idea!! (sorry you lost the battle again :smile: )

Our industry association Working Groups uses Confluence for drafting reference documents for ultimately publishing to the industry.

Each Chapter of the document is drafted directly on screen.

There are two commenting methods now (depending on your version - we have v5.8.16)

Page Comments are added at the bottom of the page in "threads", but as you have noticed there is no way to mark them as "resolved"

But the new "In-line Comment" facility allows you to highlight some text in normal View mode and a pop up box appears e.g.

image2017-2-4 9:48:57.png

That then gives a comment box that also has an option to mark it as Resolved

image2017-2-4 9:50:3.png

[](https://community.atlassian.com/t5/Confluence-questions/Managing-Review-Comments/qaq-p/215304?attachment-id=12827){#link_1185c3a7d5b14c}  
[](/tnckb94959/attachments/tnckb94959/confluence-questions/36378/1/image2017-2-4%209%3A48%3A57.png){#display_1185c3a7d5b14c_0}  
[](https://community.atlassian.com/t5/Confluence-questions/Managing-Review-Comments/qaq-p/215304?attachment-id=12828){#link_1185c3a7d5b14c_0}  
[](/tnckb94959/attachments/tnckb94959/confluence-questions/36378/2/image2017-2-4%209%3A50%3A3.png){#display_1185c3a7d5b14c_2}  
[](https://community.atlassian.com/t5/Confluence-questions/Managing-Review-Comments/qaq-p/215304?attachment-id=12829){#link_1185c3a7d5b14c_1}  
[](/tnckb94959/attachments/tnckb94959/confluence-questions/36378/3/image2017-2-4%209%3A46%3A13.png){#display_1185c3a7d5b14c_4}

comments: author: Stacy Dow
body: This is such great news! We are updating to 6 right now! SO HAPPY to see that this will get easier..... THANK YOU


author: Roman Kirilenko
body: Just for information. There is the add-on called [Talk](https://marketplace.atlassian.com/plugins/com.stiltsoft.confluence.talk.confluence-talk-plugin/server/overview). It has many [features](https://docs.stiltsoft.com/display/public/Talk/Talk+for+Confluence) and in some cases can be even better than Confluence inline comments (it allows you to insert inline comments too). You can also try it without installing to your instance, the team created a [demo server](https://demo.stiltsoft.com/display/talk/Talk+Inline+Comments+for+Confluence+Demo) for this purpose.


author: Biwords Services
body: Hi,

This is a good approach. But sometimes authors use a different tool to author content.

Can we have a plugin from the authoring tool to launch confluence.

Thanks,

Nuz


author: Adrian Hepworth
body: We use Confluence for collaborative review comments between ourselves and the customer. Inline comments work well, with footer comments being used for things like saying "I've reviewed this page, back to you for updates". One thing that was difficult is tracking all the comments collectively, so we wrote a PHP macro that walks the pages below a starting page, fetches all the comments, and then builds a single review comments page below the same parent with all the comments from all child pages on it. That helps because you can't see the actual comment when you go to edit the page to resolve it (a major flaw IMHO).

![Screen Shot 2017-04-20 at 09.09.39.png](https://community.atlassian.com/t5/image/serverpage/image-id/504iB14C70E6A5B6E949/image-size/large?v=v2&px=999 "Screen Shot 2017-04-20 at 09.09.39.png")

comments: author: hieroglyf
body: Hello Adrian,

we are looking for a similar comments tracking solution. Would you be able to share/sell such script? Does it also work for comments under the page?

Thanks for letting me know.


author: Adrian Hepworth
body: Here's the Python code (we upgraded from PHP) that produces the table above. Apologies for not replying sooner hieroglyf - I've only just seen this !

It's crude code without much error checking (don't judge!) but does the job. It should be obvious to a decent coder what needs to change to work on your instance (be it Cloud or Hosted). Feedback welcome.

Usual disclaimer, you are free to use this code, but please attribute the author. No liability will be accepted for your use of this software (it does write new pages to your instance via the brilliant API services - thanks Atlassian).

--

import requests  
import json  
from requests.auth import HTTPBasicAuth  
from pprint import pprint  
import csv, re, datetime  
import urllib.parse  
import html  
from datetime import datetime  
from html.parser import HTMLParser

# Author: Adrian Hepworth / Worldline

# Licensed under Creative Commons - CC BY-SA 4.0

# The base content API - change to point at your instance  
contenturl = '<https://organisation.atlassian.net/wiki/rest/api/content>'  
# The space ID in which the parent page resides  
space = "SPACE"  
# The Parent Page  
source = "My Page"

# Get the username and password from a local CSV file called 'atlassian-login.txt'  
with open('atlassian-login.txt', newline='') as csvfile:  
cred_reader = csv.reader(csvfile, delimiter=',', quotechar='"')  
for item in cred_reader:  
username=item\[0\]  
token=item\[1\]

auth = HTTPBasicAuth(username,token)

comment_list = \[\]

def escape(s):  
# this has to be first:  
s = s.replace("\&", "\&amp;")  
s = s.replace("\<", "\&lt;")  
s = s.replace("\>", "\&gt;")  
s = s.replace('\\\\n', "\<br /\>")  
s = s.replace('\\n', "\<br /\>")  
return s

class MLStripper(HTMLParser):  
def __init__(self):  
super().__init__()  
self.reset()  
self.fed = \[\]  
def handle_data(self, d):  
self.fed.append(d)  
def get_data(self):  
return ''.join(self.fed)

def strip_tags(html):  
s = MLStripper()  
s.feed(html)  
return s.get_data()

def find_page(space,title):  
global contenturl  
params = {'spaceKey':space,  
'title':title}  
resp = requests.get(contenturl,params=params,auth=auth)  
if resp.status_code != 200:  
return None  
results = json.loads(resp.text)  
if results\['size'\] == 0:  
return None  
page=results\['results'\]\[0\]  
return page

def get_url(url,params):  
#print("Get URL for %s with %s params" % (url,params))  
global auth  
resp = requests.get(url,params=params,auth=auth)  
if resp.status_code != 200:  
return None  
results = json.loads(resp.text)  
if results\['size'\] == 0:  
return None  
return results

def get_comment_list(id,url):

global comment_list

print("Going for %s,%s,%s with %d comments so far" % (space,id,url,len(comment_list)))

params = {'expand':'extensions.inlineProperties,extensions.resolution,history,body.view'  
,'limit':'125'  
,'depth':'all'}

comments = get_url(url+'/child/comment',params=params)

# print("There are %d comments\\nComments: %s" % (len(comments),comments)); exit()

if comments:  
for comment in comments\['results'\]:  
# print('\\nComment: %s' % comment)  
if comment\['extensions'\]\['location'\]=='inline':  
comment_list.append({'page' : comment\['title'\],  
'pageURL' : comment\['_links'\]\['self'\],  
'pageUI' : comments\['_links'\]\['base'\]+comment\['_links'\]\['webui'\],  
'location' : comment\['extensions'\]\['location'\],  
'status' : comment\['extensions'\]\['resolution'\]\['status'\],  
'selection' : comment\['extensions'\]\['inlineProperties'\]\['originalSelection'\],  
'title' : comment\['title'\],  
'who' : comment\['history'\]\['createdBy'\]\['publicName'\],  
'when' : comment\['history'\]\['createdDate'\],  
'link' : comment\['_links'\]\['self'\],  
'web' : comments\['_links'\]\['base'\]+comment\['_links'\]\['webui'\],  
'comment' : strip_tags(html.unescape(comment\['body'\]\['view'\]\['value'\]))}  
)

#print (comment_list)

# Recurse to call itself to walk the tree  
content = get_url(url+'/child/page',{'limit':150});

if content:  
for childPage in content\['results'\]:  
# Append the comments on each page  
print('Found child page: %s' % childPage\['id'\])  
get_comment_list(childPage\['id'\],childPage\['_links'\]\['self'\])

# Find the parent page  
start_page = find_page(space,source)

get_comment_list(start_page\['id'\],start_page\['_links'\]\['self'\])

body = "\<h1\>Comment Extract\</h1\>"

body += '\<table class=\\"wrapped fixed-table\\"\>\<colgroup\>\<col style=\\"width: 150px;\\" /\> \<col style=\\"width: 100px;\\" /\> \<col style=\\"width: 100px;\\" /\> \<col style=\\"width: 600px;\\" /\> \</colgroup\>\<tbody\>\<tr\>\<th\>Page\</th\>\<th\>Location\</th\>\<th\>Status\</th\>\<th\>Comments\</th\>\</tr\>';

for ix,comment in enumerate(comment_list):  
# print(comment)  
# output the comment  
if comment\['location'\] != "inline":  
# Complete new line  
if ix\>0:  
body += "\</td\>\</tr\>"  
body += '\<tr\>\<td\>\<a href=\\"'+escape(comment\['pageUI'\])+'\\"\>'+escape(comment\['page'\])+'\</a\>\</td\>'  
body +='\<td class=\\"highlight-blue\\" data-highlight-colour=\\"blue\\"\>'+comment\['location'\]+'\</td\>'  
body += '\<td\>'+comment\['status'\]+'\</td\>'  
body += '\<td\>\<a href=\\"'+escape(comment\['web'\])+'\\"\>'+comment\['who'\]+'\</a\> - '+comment\['when'\]  
body += '\<br /\>'+escape(comment\['comment'\])  
else:  
if comment\['selection'\] == "":  
# This is a reply to a comment  
body += '\<br /\>\<a href=\\"'+escape(comment\['web'\])+'\\"\>'+comment\['who'\]+'\</a\> - '+comment\['when'\]  
body += '\<br /\>'+escape(comment\['comment'\])  
else:  
if ix\>0:  
body += "\</td\>\</tr\>"  
body += '\<tr\>\<td\>\<a href=\\"'+escape(comment\['pageUI'\])+'\\"\>'+escape(comment\['page'\])+'\</a\>\</td\>'  
body += '\<td\>'+comment\['location'\]+'\</td\>'  
if comment\['status'\] == "resolved" or comment\['status'\] == "dangling":  
body += '\<td class=\\"highlight-green\\" data-highlight-colour=\\"green\\"\>'+comment\['status'\]+'\</td\>'  
else:  
body += '\<td\>'+comment\['status'\]+'\</td\>'  
body += '\<td\>\<span style=\\"color: blue;\\"\>\<em\>'+escape(comment\['selection'\])+'\</em\>\</span\>'  
body += '\<br /\>\<a href=\\"'+escape(comment\['web'\])+'\\"\>'+comment\['who'\]+'\</a\> - '+comment\['when'\]  
body += '\<br /\>'+escape(comment\['comment'\])

<br />

body += '\</td\>\</tr\>\</tbody\>\</table\>'

title = "Comments extracted on %s" % datetime.today().strftime('%Y-%m-%d %H:%M:%S')  
header = {'Content-Type': 'application/json'}  
reqbody = {"type":"page",  
"title":title,  
"space":{"key":space},  
"ancestors":\[{"id":start_page\['id'\]}\],  
"metadata":{"labels":\[{"name":"no-print"}\]},  
"body":{"storage":{"value":body,  
"representation":"storage"}}}

resp = requests.post(contenturl,headers=header,json=reqbody,auth=auth)

if resp.status_code != 200:  
print('Unable to process creation - response %s' % resp.status_code)  
print('Response: %s' % resp.text)  
exit()

page = resp.json()

print('Successfully created page id (%s)\\n' % page\['id'\])

exit()


author: Adrian Hepworth
body: You'll need to re-tab the code as it's pasted without correct Python tabs.