DEV Community

Cover image for Solved: Integrating Asana with Bitbucket: Update Tasks on Commit
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Integrating Asana with Bitbucket: Update Tasks on Commit

🚀 Executive Summary

TL;DR: This guide addresses the problem of manual Asana task updates during development by integrating Bitbucket commits with Asana. It provides a step-by-step solution using Bitbucket webhooks and a custom Python Flask service to automatically add commit details as comments to relevant Asana tasks, improving visibility and reducing manual overhead.

🎯 Key Takeaways

  • Asana API access is secured using a Personal Access Token (PAT), which should be stored as an environment variable for security.
  • A custom Python Flask application acts as a webhook listener, parsing Bitbucket ‘repo:push’ event payloads.
  • Regular expressions (ASANA\_TASK\_ID\_REGEX) are used to extract Asana Global IDs (GIDs) from commit messages (e.g., ‘ASANA-1234567890’ or ‘#1234567890’).
  • The Flask service adds commit details as ‘stories’ (comments) to the identified Asana tasks via the Asana API’s /tasks/{task\_gid}/stories endpoint.
  • The Python service must be publicly accessible (e.g., via ngrok for testing or cloud deployment for production) for Bitbucket webhooks to reach it, and the Bitbucket webhook must be configured to trigger on ‘Repository push’ events.

Integrating Asana with Bitbucket: Update Tasks on Commit

As a development team grows, so does the complexity of managing tasks and code changes. Developers often find themselves in a constant state of context-switching, moving between their code editor and project management tools like Asana to update task statuses, add comments, or link relevant commits. This manual effort is not only tedious but also prone to errors and delays, ultimately impacting team productivity and the accuracy of project tracking.

At TechResolve, we believe in automating the mundane to free up engineers for more impactful work. This tutorial will guide you through integrating Bitbucket with Asana, enabling automatic task updates whenever a relevant commit is pushed to your repository. Imagine a world where your Asana tasks are automatically commented with commit details, ensuring stakeholders always have the latest information without a single manual intervention from your development team.

By leveraging Bitbucket webhooks and a small, custom Python service, we will create a seamless workflow that bridges the gap between your version control and project management systems. This integration will significantly improve visibility, reduce manual overhead, and keep your Asana boards consistently up-to-date.

Prerequisites

Before we dive into the integration, ensure you have the following:

  • An Asana Account: With permissions to create Personal Access Tokens and modify tasks within your desired workspace/project.
  • A Bitbucket Cloud Account: With administrative access to a repository to configure webhooks.
  • Python 3.x Installed: Along with pip for package management.
  • Internet Connectivity: Your custom service will need to be publicly accessible to receive Bitbucket webhook payloads. For local testing, tools like ngrok are invaluable.

Step-by-Step Guide

Step 1: Secure Your Asana API Access

To interact with the Asana API, you’ll need a Personal Access Token (PAT). This token acts as your password for API requests, so treat it with the utmost care and never hardcode it directly into your scripts.

  1. Log in to your Asana account.
  2. Navigate to your profile settings by clicking your profile picture in the top right corner, then selecting My Settings.
  3. Go to the Developer App tab.
  4. Under “Personal Access Tokens”, click Create new personal access token.
  5. Give it a descriptive name (e.g., “Bitbucket Integration”) and click Create token.
  6. Copy the token immediately. Asana will only show it once.

For security, we recommend storing this token as an environment variable in your deployment environment. For local development, you might use a .env file or simply export it in your terminal.

export ASANA_ACCESS_TOKEN="your_asana_personal_access_token_here"
Enter fullscreen mode Exit fullscreen mode

Step 2: Develop the Webhook Listener (Python Flask)

We’ll create a simple Flask application that will listen for incoming POST requests from Bitbucket. When a request is received, it will parse the commit details, extract the Asana task ID (which we’ll embed in the commit message), and then use the Asana API to update that task.

First, install the necessary Python packages:

pip install Flask requests
Enter fullscreen mode Exit fullscreen mode

Now, create a Python file (e.g., app.py) with the following content:

import os
import re
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

# Asana API configuration
ASANA_API_BASE_URL = "https://app.asana.com/api/1.0"
ASANA_ACCESS_TOKEN = os.environ.get("ASANA_ACCESS_TOKEN")

if not ASANA_ACCESS_TOKEN:
    print("Error: ASANA_ACCESS_TOKEN environment variable not set.")
    exit(1)

# Regex to find Asana task GIDs in commit messages (e.g., "ASANA-1234567890", "#1234567890")
# Asana task GIDs are long numerical IDs.
ASANA_TASK_ID_REGEX = re.compile(r"(ASANA-(\d+))|(\D*#(\d+))")

def find_asana_task_gid(message):
    """
    Extracts Asana task GID from a commit message.
    Assumes task GID is prefixed with 'ASANA-' or '#'.
    For example: "Fix bug for ASANA-1234567890", "Refactor module #1234567890"
    """
    match = ASANA_TASK_ID_REGEX.search(message)
    if match:
        if match.group(2):  # Matched ASANA- followed by digits
            return match.group(2)
        elif match.group(4): # Matched # followed by digits
            return match.group(4)
    return None

def add_comment_to_asana_task(task_gid, comment_text):
    """
    Adds a comment (story) to an Asana task using its GID.
    """
    headers = {
        "Authorization": f"Bearer {ASANA_ACCESS_TOKEN}",
        "Content-Type": "application/json"
    }

    # Ensure the task_gid is purely numerical as required by Asana API
    if not task_gid.isdigit():
        print(f"Error: Provided task GID '{task_gid}' is not purely numerical. Cannot update task.")
        return False

    url = f"{ASANA_API_BASE_URL}/tasks/{task_gid}/stories"
    data = {
        "data": {
            "text": comment_text
        }
    }
    response = requests.post(url, headers=headers, json=data)

    if response.status_code == 201:
        print(f"Successfully added comment to Asana task {task_gid}.")
        return True
    else:
        print(f"Failed to add comment to Asana task {task_gid}. Status: {response.status_code}, Response: {response.text}")
        return False

@app.route("/bitbucket-webhook", methods=["POST"])
def bitbucket_webhook():
    payload = request.get_json()

    if not payload:
        return jsonify({"message": "Invalid JSON payload"}), 400

    # Bitbucket 'repo:push' event structure
    if "push" in payload and "changes" in payload["push"]:
        for change in payload["push"]["changes"]:
            if "new" in change and "target" in change["new"] and "commits" in change["new"]["target"]:
                for commit in change["new"]["target"]["commits"]:
                    commit_message = commit.get("message", "")
                    commit_hash = commit.get("hash", "")
                    commit_author = commit.get("author", {}).get("user", {}).get("display_name", "Unknown Author")
                    commit_url = commit.get("links", {}).get("html", {}).get("href", "#")

                    print(f"Processing commit: {commit_hash} by {commit_author}")
                    print(f"Message: {commit_message}")

                    asana_task_gid = find_asana_task_gid(commit_message)

                    if asana_task_gid:
                        comment = (
                            f"Commit {commit_hash} by {commit_author} pushed to Bitbucket:\n"
                            f"Message: {commit_message.strip()}\n"
                            f"Link: {commit_url}"
                        )
                        print(f"Found Asana task GID: {asana_task_gid}. Attempting to add comment...")
                        add_comment_to_asana_task(asana_task_gid, comment)
                    else:
                        print("No Asana task GID found in commit message.")
            else:
                print("No new commits found in this push event, or unexpected structure.")
    else:
        print("Not a 'repo:push' event or unexpected payload structure.")

    return jsonify({"message": "Webhook processed"}), 200

if __name__ == "__main__":
    # For local development:
    # Set ASANA_ACCESS_TOKEN environment variable before running.
    # Then run with: python app.py
    # This will run on http://127.0.0.1:5000
    app.run(debug=True, port=5000)
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code:

  • The script initializes a Flask app and securely retrieves your Asana Personal Access Token from environment variables.
  • The ASANA_TASK_ID_REGEX uses a regular expression to find patterns like ASANA-1234567890 or #1234567890 within your commit messages. It’s crucial that the extracted number is the actual Global ID (GID) of your Asana task. Asana GIDs are long numerical identifiers.
  • The find_asana_task_gid function extracts this numerical GID from the commit message.
  • The add_comment_to_asana_task function constructs an API call to Asana’s /tasks/{task_gid}/stories endpoint, adding a new comment (which Asana calls a “story”) with the commit details.
  • The /bitbucket-webhook endpoint listens for POST requests. It parses the JSON payload sent by Bitbucket, iterates through the commits, and if an Asana task GID is found in a commit message, it calls add_comment_to_asana_task.
  • The if __name__ == "__main__": block runs the Flask development server on port 5000.

Step 3: Deploy and Expose Your Service

For Bitbucket to send webhook payloads, your Flask application needs to be running and accessible via a public URL. For testing, ngrok is an excellent choice. For production, consider platforms like AWS Lambda, Google Cloud Run, or a dedicated server.

  1. Run your Flask app locally:
# Make sure your ASANA_ACCESS_TOKEN is set in your environment
python app.py
Enter fullscreen mode Exit fullscreen mode

Your app should now be running on http://127.00.1:5000.

  1. Expose your local server using ngrok:
ngrok http 5000
Enter fullscreen mode Exit fullscreen mode

ngrok will provide you with a public HTTPS URL (e.g., https://abcdef12345.ngrok.io). This is the URL Bitbucket will send requests to.

For production deployments, you would deploy app.py to a cloud service (e.g., a container on Google Cloud Run or AWS ECS, or a serverless function) and obtain its public endpoint.

Step 4: Configure Bitbucket Webhook

Now, let’s tell Bitbucket where to send commit events.

  1. Log in to Bitbucket and navigate to your repository.
  2. Go to Repository settings (usually in the left sidebar).
  3. Under the “Workflow” section, click on Webhooks.
  4. Click Add webhook.
  5. Title: Give it a descriptive name, like “Asana Integration Webhook”.
  6. URL: Paste the public URL of your Flask application (the ngrok URL, or your deployed service’s URL), followed by the endpoint path: https://your-public-url.com/bitbucket-webhook.
  7. Status: Ensure it’s set to “Active”.
  8. Triggers: Select Choose from a full list of triggers and enable only Repository push. This ensures the webhook only fires when code is pushed.
  9. Click Save.

Test the Integration:

Commit a change to your Bitbucket repository with a message that includes an Asana task GID, for example:

git commit -m "Fixing authentication flow for ASANA-1234567890. Closes #bug."
Enter fullscreen mode Exit fullscreen mode

Replace 1234567890 with an actual Asana task’s GID (Global ID) from your workspace. You can find this GID in the URL of your Asana task (e.g., https://app.asana.com/-/0/PROJECT_GID/TASK_GID).

Then, push your commit:

git push origin main
Enter fullscreen mode Exit fullscreen mode

Monitor your Flask app’s console (or ngrok‘s interface) for incoming requests and check your Asana task. You should see a new comment containing the commit details.

Common Pitfalls

  • Incorrect Asana Access Token: Double-check your PAT. Ensure it has not expired and has the necessary permissions (it should, by default, allow adding comments).
  • Webhook URL Inaccessibility: Your Flask app must be publicly reachable. If using ngrok, ensure it’s running. If deployed, check firewalls and network configurations.
  • Bitbucket Webhook Trigger Settings: Verify that “Repository push” is selected as the trigger. Check the webhook’s “Recent deliveries” in Bitbucket settings to see if it’s being sent and if there are any errors.
  • Asana Task GID Mismatch: The regex in your Python script must accurately extract the Asana task’s numerical GID. If your commit messages use a different format, adjust the ASANA_TASK_ID_REGEX accordingly. If you use custom textual IDs in Asana, you will need a more complex interaction with the Asana API to search for tasks by that custom field first to retrieve its GID.
  • Bitbucket Secret Configuration: For enhanced security, Bitbucket webhooks can send a secret header (X-Hub-Signature). Our current Flask app doesn’t validate this, but for production, you should verify the signature to ensure requests are genuinely from Bitbucket.

Conclusion

Congratulations! You’ve successfully integrated Bitbucket with Asana, automating the critical step of updating project tasks based on code commits. This automation streamlines your development workflow, reduces manual effort, and ensures that your project management tool always reflects the latest state of your codebase. No more outdated tasks or endless questions about commit relevance!

This tutorial provides a solid foundation. From here, you can extend this integration further:

  • Change Task Status: Based on specific keywords in commit messages (e.g., “resolves #1234567890”), automatically mark tasks as complete or move them to a different section.
  • Assign Tasks: Assign tasks to reviewers or QA based on commit authors or branch names.
  • Error Handling and Logging: Implement robust error handling, detailed logging, and alerting for production environments.
  • Deployment Strategy: Move from ngrok to a more permanent, scalable, and secure hosting solution like serverless functions (AWS Lambda, Google Cloud Functions) or containerized deployments.

By continually optimizing these types of integrations, your team can focus more on innovation and less on administrative overhead, truly embodying the DevOps spirit of efficiency and collaboration. Happy coding!


Darian Vance

👉 Read the original article on TechResolve.blog


Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)