Updating a confluence page with Rest API: problem with ancestors

Hello all,

I've recently switched from the SOAP api to the rest API to create and update confluence pages (I'm using Confluence 5.5.6).

I would like to report a strange behaviour with the API when updating a page.

First, it's important to know that if you use the example from the Rest API example page ( https://developer.atlassian.com/display/CONFDEV/Confluence+REST+API+Examples ), your page will loose its ancestor. The page ancestor is deleted and the page ancestor is its space: you cannot browse it with the treeview. In order to avoid that problem, you have to provide the ancestor in the request.

Second, to update a JIRA issue or a confluence page with the REST API, I send a request with all the elements needed to update the issue or the page, I modify the JSON received and I send it back (it seems to be pretty obvious behaviour).

So in order to update a confluence page the minimal request has this form: http://<host>:8080/confluence/rest/api/content/<id>?expand=body.storage,version,ancestors

body.storage and version are updated in the JSON and a PUT request is sent back.

Unfortunately, if you do so, your page will still loose its ancestor... And it took me a while to figure out why: the returned JSON has ALL the ancestors of the page: the direct ancestor of the page, the ancestor of the ancestor and so on until the space root. So the array has several ancestors... And if you don't remove them except the direct ancestor, your page will loose its ancestor.

So I wonder, anyone else came accross that problem ? Is it a normal behaviour or a bug/feature ?

Any opinions welcome.

Fabio

4 answers

I'm in the process of moving code to using the REST API from the XML-RPC API. I ran into this problem, and here's how I worked around it.

I need to preface this with a warning that (as you've probably discovered) the Confluence documentation isn't very good. Either there's still a lot of functionality not available in the REST API yet, or it's not documented. Either way, the solution I have here is based on my own observations, and not on any documentation I've read.

I'm using Confluence 5.6.3, interacting with it using Perl and the JSON, URI, and LWP libraries.

For starters, I have not found a way to get the immediate ancestor of a page (I'll be posting a question about this). As you've observed, you can only get a list of all ancestors. However, I noticed that the list is ordered (usually, more on this below) such that the last item on the list is the immediate parent of the page I'm working with. So after parsing the JSON-formatted string and getting a list, I pull the page ID from the last item in the list, and use that.

In another post somewhere someone said to only set the ID of the page's immediate ancestor. There's no need to set it for all the parents. Something like this:

..., "ancestors":[{"id", "12345"}], ...

You're essentially giving it a one-element list, with the ID of the immediate ancestor. This seems to work.

Now, a big warning: I've come across circumstances when the list of ancestors returned by the REST API is not in order, and even contains duplicate entries. This appears to happen only when I've fouled up a page's ancestry while testing and debugging code. The pages all end up on the space root, and in the wiki I manually drag them back into the proper order, Everything on the wiki looks good, but after doing this the ancestor list I get from the REST API is no longer nicely ordered.

One workaround to this might be to sort the ancestors by page ID number. On our system I noticed that the further down the tree I go, the higher the page numbers are. So if I sort the ancestor list and then take the highest-numbered ID, that should be the ID I want.

I hope this is helpful. If there's anything that needs clarifying let me know.

 

-Josh

Thanks! This worked like a charm for me.

I took Joshua Senecal's response a bit further and I think I address his caveat - before uploading a new version of the page (from a Python script), I find the id of the direct ancestor of that page. The method looks at the page's "ancestors" attribute:

  1. If there is no ancestor found (attribute is empty) then this is a "root" page, and I drop the "ancestor" entry from the PUT payload.
  2. If there is only one ancestor then use it (e.g. the page is probably a direct descendent of the root page, but that doesn't matter to the code)
  3. If there are more than one ancestors then loop through them, fetch their "/content/<id>/child/page" and see whether the current page's id appears there, if so then use that ancestor.

With this code I could update a space of 342 pages at different tree depths without moving the pages. They also maintained their order relative to their siblings.

Here is the python code, in this code:

  1. page_tree is a dictionary with keys being page id's and values being set()'s of children id's
  2. get() is a simple wrapper around requests
  3. 'url' is the base URL to the Confluence instance root
page_tree = dict()
API_URL_PATH = '/rest/api'


def get_parent_id(user, password, space_key, url, page):
  if not 'ancestors' in page or len(page['ancestors']) == 0:
    return None
  if len(page['ancestors']) == 1:
    return page['ancestors'][0]['id']
  page_id = page['id']
  for ancestor in reversed(page['ancestors']):
    parent_id = ancestor['id']
    if parent_id not in page_tree:
      page_tree[parent_id] = get_page_children(user, password, space_key, url, parent_id)
    if page_id in page_tree[parent_id]:
      return parent_id
  return None
 
def get_page_children(user, password, space_key, url, page_id):
  children = set()
  location = url + API_URL_PATH + ('/content/%s/child/page' % page_id)
  while True:
    response = get(user, password, location, { 'spaceKey': space_key })
    children.update(map(lambda child: child['id'], response['results']))
    if 'next' in response['_links']:
      location = url + response['_links']['next']
    else:
      break


  return children

Very nice! This is likely the sort of thing I'll have to implement as well to ensure my code is robust.

What exactly do you do in the wrapper method get(...) (line:23)?

Here is a rough version of get:

def get(user, password, payload, url):
  request = requests.Request('POST', url, data = json.dumps(payload), headers = headers = {
    'content-type': 'application/json',
    'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (user, password)).replace('\n', '')
  })

  prepared = request.prepare()
  return session.send(prepared)

Hello,

Thank you for your feedback!

Can we all agree it's a bug ? smile

By bug I mean that you request some data, you modify it and you cannot send it back as it is, you have to clean it otherwise the ancestors are lost.

Anyway unlike Joshua I've never came across a case where the ancestors list is not sorted (the first one in the list is the farthest one). But of course Amos's method is the safest one.

 

 

Yes, I'd say it's a bug.

Has this been logged with Atlassian? If that's the case, it would be good to post here the ticket number

No I haven't logged an issue about this particular problem.

I have logged this issue, please add any comments you consider relevant: [CRA-487](https://jira.atlassian.com/browse/CRA-487)

Update - the issue was marked as Resolved in version 5.8 (and I assumed current cloud version, dunno how to see this).

0 votes

Hi Everyone,

This is a known bug in confluence. It has been fixed in 5.8 and above:

 

Suggest an answer

Log in or Sign up to answer
Community showcase
Posted Oct 24, 2018 in Confluence

Atlassian Research opportunity with Confluence templates

Do you use templates with Confluence? Take part in a remote 1-hr workshop. You'll receive USD $100 for your time!   We're looking for people to participate in a   remote 1-hr workshop...

1,059 views 16 14
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you