Comments containing @user name return a url to a task list

Here is the scenario:
There is a task, this task has comments, some of those comments contain @username in order to get that users attention.

If you retrieve that comment via the API ( very convoluted as it is ) it has replaced the username with a link. That link is a link to the users task list, for example Log in - Asana

This is not ideal, firstly we have no way of determining from that URL if it’s just a URL someone pasted or if it’s a link to a user. If it’s a link to that user, then what is the users name ?

Ok fine, so, let’s get a list of all users and cache them with their IDs then we can use regex to see if that url contains a user ID. Oh wait, that URL has no relation to the users ID. The ID in the URL is some randomly ID that’s specifically the link to that users task list.

So I have to questions

  1. Is there anyway I can reverse engineer the URL returned to find out what the user is ?
  2. Can Asana please implement a better solution to @username in a comment.

I have a proposed solution for 2)

Asana Task Comment : “Hello please see this ticket, it needs your attention”

API Response

{
   id : 11111,
   resource_subtype : "comment_added"
   type : "comment"
   text : "Hello %s please see this ticket, it needs your attention"
   bindings : [{type: "user", value: "John Doe", link : "https://app.asana.com/0/11111111111/list"}]
}

Yes, you can get the user’s ID directly. What you want is to be using the rich text version of the comment, which for comments is html_text, specifically the <a> tag. See the discussion of the <a> tag here:

2 Likes

I am not receiving a rich text value from the api. Is there an additional parameter required when making the request to receive this information ?

I’m using the /tasks/{taskId}/stories endpoint.

Ah, yeah - for performance reasons, the API doesn’t return all fields (the principle being, why send you extra info if you don’t need it?). Try adding a parameter of opt_fields=html_text, so:
tasks/{taskId}/stories?opt_fields=html_text

You can read more about opt_fields here:

2 Likes

This does seem to work, but there is a gotcha.

If you specify opt_fields, it will only return the opt_fields and the id/gid as apposed to appending the opt_field to the normal set of returned data.

The created_by property then also loses the users name and just returns the id :frowning:

Yes, that’s true.

The solution is to do this instead:
tasks/{taskId}/stories?opt_expand=html_text,created_by

You can add additional fields beyond these two if you want those included in the returned data as well.

opt_expand tells Asana to return the fully expanded form of any sub-objects, like the User object for created_by. The html_text field isn’t an object so it just returns the value the same as opt_fields does.

Sadly using opt_expand=created_by does not return any expanded information. Only id, gid and resource_type

Example URI : https://app.asana.com/api/1.0/tasks/1117808720531898/stories?limit=50&opt_fields=html_text%2C+created_by%2C+text%2C+created_at%2C+type&opt_expand=created_by

created_by object

"created_by":{"id":{userId},"gid":"{userId}","resource_type":"user"}

Same thing with assignee when getting a task by ID

https://app.asana.com/api/1.0/tasks/1117808720531898?opt_fields=name%2C+assignee%2C+completed_at%2C+notes%2C+html_notes%2C+resource_subtype&opt_expand=assignee

"assignee":{"id":{userId},"gid":"{userId}","resource_type":"user"}

Sorry, I should have been clearer - by “instead” I meant use opt_expand and not opt_fields (note my example). They are mutually exclusive - you can only use one or the other, and opt_fields takes precedence.

FYI I realize this is pretty subtle but in Asana’s defense, this is documented on the API page I pointed to above :slight_smile:

If the 'fields' option is also used, it will take precedence over the 'expand' option and prevent expansion.

Classic developer who doesn’t read the documentation.

Well if that’s how it is, then that’s how it is. The end result is that I’m hammering the API so it’s to Asanas detriment. It just means that things like user information needs to be cached my end so that I can look up a full users data by their ID rather than making an API request per comment. But i’m sure other developers won’t do this.

But who does, really? :laughing:

I don’t disagree with your assessment. The only comment I’d make is that here we’re evaluating the API design based on one particular use case in which the design is inefficient; taken as a whole it may well be that the current design is the most efficient trade-off.

2 Likes

Thanks for all the helpful discussion on this page, we encountered the exact same confusion (@mentions displaying as URLs to a user’s task list, rather than their @handle, as you’d expect).

We’re writing our integration in Ruby so it took me a few minutes to figure out the correct syntactic sugar to request that html_text be included in Asana’s response when retrieving a story resource. Here’s the code for anyone else who might be struggling:

client = Asana::Client.new do |c|
  c.authentication :access_token, 'personal_access_token'
end

client.stories.get_story(story_gid: "abc123", options: {expand: ['html_text']})

Then, if you want to strip out the HTML leaving just the plaintext with the user’s @handle, you can make use of of the Rails sanitize helpers, assuming you’re using Ruby on Rails, of course

ActionView::Base.full_sanitizer.sanitize(story.html_text)