We recently migrated to Asana for project management and in the process I've created some tools that might be helpful to you too :).

Note: We're on the Enterprise plan for Asana and have a Service Account Token set up and saved to a Global Attribute in Rock. If you're on a different plan, the code below may not work or may need some modification.

In updating some code to make shareable here, I also put our Asana workspace gid into a Global Attribute.

Outline

Asana Projects - Persisted Dataset

To make searching, etc easier, we use a Persisted Dataset to load our list of projects. Here's the code it's running:

//- Get all Projects from Asana (for Persisted Dataset Update)
{% capture headers %}authorization^Bearer {{ 'Global' | Attribute:'AsanaAPIKey' }}{% endcapture %}

//- Get List of Teams to Iterate Through
{% assign workspaceGid = 'Global' | Attribute:'AsanaWorkspaceId' %}
{% assign url = 'https://app.asana.com/api/1.0/workspaces/' | Append:workspaceGid | Append:'/teams' %}
{% webrequest url:'{{ url }}' headers:'{{ headers }}' return:'Teams' %}
{% assign teams = Teams.data | Select:'gid' %}
{% endwebrequest %}
{% for team in teams %}
    {% webrequest url:'https://app.asana.com/api/1.0/teams/{{ team }}/projects' headers:'{{ headers }}' return:'Projects' %}
        {% for project in Projects.data %}
            {% assign projectSelection = project.gid | Append:'|||' | Append:project.name %}
            {% assign Selections = Selections | AddToArray:projectSelection %}
        {% endfor %}
    {% endwebrequest %}
{% endfor %}
[
    {%- for selection in Selections -%}
    {
    "Id": {{ selection | Split:'|||' | First | ToJSON }},
    "Name": {{ selection | Split:'|||' | Last | ToJSON }}
    }
    {%- unless forloop.last %},{% endunless -%}
    {%- endfor -%}
]

Asana Projects List Page

To interact with Asana projects via their API, you'll need access to the gids for the various Projects/Sections/Custom Fields, so I created a page to view them.

It starts with a Page Parameter Filter block to create a searchable list of Projects in Asana. It references the persisted dataset above to pull a list of Asana projects and add the gid to the Project page parameter.

{% assign data = 'asanaprojects' | PersistedDataset | Sort:'Name' %}
{% for item in data %}
    {{ item.Id }}^{{ item.Name | Remove:',' }}
    {% unless forloop.last %},{% endunless %}
{% endfor %}

The second block is an HTML block that runs 3 webrequests, to get the Sections, Custom Fields, and Templates:

{% assign projectId = PageParameter.Project %}
{% if projectId != null and projectId != empty %}
{% capture headers %}authorization^Bearer {{ 'Global' | Attribute:'AsanaAPIKey' }}{% endcapture %}
//- Get Sections
{% assign url = 'https://app.asana.com/api/1.0/projects/' | Append:projectId | Append:'/sections' %}
{% webrequest url:'{{ url }}' headers:'{{ headers }}' return:'Sections' %}{% endwebrequest %}
//- Get Custom Fields
{% assign url = 'https://app.asana.com/api/1.0/projects/' | Append:projectId | Append:'/custom_field_settings' %}
{% webrequest url:'{{ url }}' headers:'{{ headers }}' return:'CustomFields' %}{% endwebrequest %}
//- Get Templates
{% assign url = 'https://app.asana.com/api/1.0/task_templates?project=' | Append:projectId %}
{% webrequest url:'{{ url }}' headers:'{{ headers }}' return:'Templates' %}{% endwebrequest %}

{% capture json %}
{
    "Id": {{ projectId | ToJSON }},
    "Sections": [
            {% for section in Sections.data %}
            {
                "Id": {{ section.gid | ToJSON }},
                "Name": {{ section.name | ToJSON }}
            }{% unless forloop.last %},{% endunless %}
            {% endfor %}
        ],
    "CustomFields": [
            {% for field in CustomFields.data %}
            {
                "Id": {{ field.custom_field.gid | ToJSON }},
                "Name": {{ field.custom_field.name | ToJSON }}
                {%- if field.custom_field.enum_options != null and field.custom_field.enum_options != empty -%}
                {%- assign options = field.custom_field.enum_options -%}
                {% capture options %}
                {%- for option in options -%}
                {{ option.gid }} - {{ option.name }}
{%- endfor -%} {% endcapture %} , "Options": {{ options | ToJSON }} {%- endif -%} }{% unless forloop.last %},{% endunless %} {% endfor %} ], "Templates": [ {% for template in Templates.data %} { "Id": {{ template.gid | ToJSON }}, "Name": {{ template.name | ToJSON }} }{% unless forloop.last %},{% endunless %} {% endfor %} ] } {% endcapture %} {% assign data = json | FromJSON %} {[ rocktable title:'Sections' iconcss:'fa fa-user' fields:'Section Id,Name' ]} {% for section in data.Sections %} [[ row data:'{{ section.Id }},{{ section.Name }}' ]][[ endrow ]] {% endfor %} {[ endrocktable ]} {[ rocktable title:'Custom Fields' iconcss:'fa fa-user' fields:'Custom Field Id,Name,Options' ]} {% for field in data.CustomFields %} [[ row data:'{{ field.Id }},{{ field.Name }}, {{ field.Options }}' ]][[ endrow ]] {% endfor %} {[ endrocktable ]} {[ rocktable title:'Templates' iconcss:'fa fa-user' fields:'Template Id,Name' ]} {% for template in data.Templates %} [[ row data:'{{ template.Id }},{{ template.Name }}' ]][[ endrow ]] {% endfor %} {[ endrocktable ]} {% endif %}

well... turns out that code relies on a shortcode I made for showing a grid in Rock, so you get a bonus mini-recipe:

Rocktable Lava Shortcode

Tag Name: rocktable
Tab Type: Block
Documentation:

{[ rocktable title:'Title' iconcss:'fa fa-user' fields:'Name,Email,Phone' ]}
    {% for person in People %}
        [[ row data:'{{ person.FullName }},{{ person.Email }},{{ person | PhoneNumber:'Mobile' }}' ]][[ endrow ]]
    {% endfor %}
{[ endrocktable ]}

Shortcode Markup

<div class="panel panel-block">
    <div class="panel-body">
        <div class="grid grid-panel">
            <div class="table-responsive">
                <table class="grid-table table table-bordered table-striped table-hover">
                    <thead>
                        <tr>
                            {% for field in fields %}
                                <th>{{ field }}</th>
                            {% endfor %}
                        </tr>
                    </thead>
                    <tbody>
                        {% for row in rows %}
                        <tr>
                            <td>{{ row.data | Split:',' | Join:'' }}</td>
                        </tr>
                        {% endfor %}
                    </tbody> 
                </table>
            </div>
        </div>
    </div>
</div>

Parameters: title,iconcss,fields

Set Asana Project Admin

This is a workflow type that will let you select an Asana Project and add a user as an admin to that project. This was immensely helpful as we imported projects from our old project management solution. Here's the workflow to import:

Download Workflow Type

Asana Id - Person Attribute

You'll notice that the workflow above uses a Person Attribute "Asana Id". We have a job that runs nightly to sync in any new Asana users. If it can match them to a person in Rock, it updates the attribute. If not, it creates a new person with a Connection Status of "New From Asana" and send the Rock Admins an email so they can merge them with the correct individual (or delete the Asana user created in error).

Create Asana Task - Lava Shortcode

The last piece we made was a shortcode to handle creating a task in Asana. We have a number of workflows that use this shortcode to create a task based on the results of a form. It handles setting the due date, assigning the task to a user (using their Asana Id attribute) as well as filling any custom fields.

Name: Create Asana Task
Tag Name: asanatask
Tag Type: Block
Documentation:

Create an Asana task by specifying the parent project, a section (optional), an assignee (optional), a due date (optional), and a card name. The body of the shortcode will be inserted as the task description (html allowed).

{[ asanatask project:'1206823362805504' assignee:'1206823362805504' section:'1206823362805504' name:'Asana Test Card' due:'5/1/2024' ]}
    <strong>Attribute1: </strong>: AttributeValue
    <strong>Attribute2: </strong>: AttributeValue
{[ endasanatask ]}
You can also include custom field data by providing the custom field id, type, and value:

{[ asanatask project:'1206823362805504' assignee:'1206823362805504' section:'1206823362805504' name:'Asana Test Card' due:'5/1/2024' ]}
    [[ customfield id:'1206823362805504' type:'date' value:'2024-02-10' ]][[ endcustomfield ]]
    [[ customfield id:'1207182546966284' type:'person' value:'1206823362805504' ]][[ endcustomfield ]]
    <strong>Attribute1: </strong>: AttributeValue
    <strong>Attribute2: </strong>: AttributeValue
{[ endasanatask ]}

Shortcode Markup:

{% capture data %}
{
  "data": {
  {% if customfields != null and customfields != empty %}
    "custom_fields": {
        {% for field in customfields %}
            {% if field.type == "date" %}
                "{{ field.id }}": {"date": {{ field.value | ToJSON }}}
            {% elseif field.type == "person" %}
                "{{ field.id }}": [{{ field.value | ToJSON }}]
            {% else %}
                "{{ field.id }}": {{ field.value | ToJSON }}
            {% endif %}{% unless forloop.last %},{% endunless %}
      {% endfor %}
    },
    {% endif %}
    "name": {{ name | ToJSON }},
    "html_notes": {{ blockContent | Prepend:'' | Append:'' | ToJSON }}
    {% if template == null or template == empty %}
    ,"projects": [
      "{{ project }}"
    ]
    {% endif %}
    {% if assignee != null and assignee != empty %}
    , "assignee": {{ assignee | ToJSON }}
    {% endif %}
    {% if due != null and due != empty %}
    , "due_on": {{ due | Date:'yyyy-MM-dd' | ToJSON }}
    {% endif %}
  }
}
{% endcapture %}
{% assign asanaKey = 'Global' | Attribute:'AsanaAPIKey' %}

//- If template, create
{% if template != null and template != empty %}
    {% capture templateData %}
    {
      "data": {
        "name": {{ name | ToJSON }},
        "include": "assignee,attachments,dependencies,parent,projects,subtasks,tags"
        }
    }
    {% endcapture %}
    {% assign url = 'https://app.asana.com/api/1.0/tasks/' | Append:template | Append:'/duplicate' %}
    {% webrequest url:'{{ url }}' headers:'authorization^Bearer {{ asanaKey }}' method:'POST' body:'{{ templateData }}' requestcontenttype:'application/json' %}
    {{ results }}
{% assign results = results | RemoveFirst:'Created: ' | FromJSON %} {% assign taskId = results.data.new_task.gid %} {% endwebrequest %} {% endif %} TaskId: {{ taskId }}
//- Create Card (or update if already created by from template) {% assign url = 'https://app.asana.com/api/1.0/tasks' %} {% assign method = 'POST' %} {% if taskId != null and taskId != empty %} {% assign url = url | Append:'/' | Append:taskId %} {% assign method = 'PUT' %} {% endif %} {% webrequest url:'{{ url }}' headers:'authorization^Bearer {{ asanaKey }}' method:'{{ method }}' body:'{{ data }}' requestcontenttype:'application/json' %} {{ results }}
{% endwebrequest %} {% assign results = results | RemoveFirst:'Created: ' | FromJSON %} {% if taskId == null or taskId == empty %} {% assign taskId = results.data.gid %} {% endif %} //- Remove Integration from Followers {% assign url = 'https://app.asana.com/api/1.0/tasks/' | Append:taskId | Append:'/removeFollowers' %} {% capture data %} { "data": { "followers": [ "1205703632291156" ] } } {% endcapture %} {% webrequest url:'{{ url }}' headers:'authorization^Bearer {{ asanaKey }}' method:'POST' body:'{{ data }}' requestcontenttype:'application/json' %} {{ results }}
{% endwebrequest %} {% if section != null and section != empty %} {% assign url = 'https://app.asana.com/api/1.0/sections/' | Append:section | Append:'/addTask' %} {% capture data %} { "data": { "task": {{ taskId | ToJSON }} } } {% endcapture %} {% webrequest url:'{{ url }}' headers:'authorization^Bearer {{ asanaKey }}' method:'POST' body:'{{ data }}' requestcontenttype:'application/json' %} {{ results }}
{% endwebrequest %} {% endif %}

Parameters: name,project,assignee,due,section,template

Note: You'll see some references to "template" in this code. That is a work in progress. I haven't quite figured out how to create a task with a template yet.