🔗

GitHub IssuesとPull RequestsをAsanaタスクに自動連携する仕組みを作った話

に公開

はじめに

エンジニアチームでは、タスク管理ツールとGitHubの二重管理に悩まされることはありませんか?

私たちのチームでは、プロジェクト管理にAsanaを使用していますが、実際の開発作業はGitHub上で行われます。この結果、以下のような課題が発生していました:

  • GitHub Issueを作成したら、Asanaにも同じタスクを手動で作成する必要がある
  • IssueをクローズしたらAsanaタスクも手動で完了にする必要がある
  • コメントやステータスの更新を両方のツールで行う必要がある
  • どちらかの更新を忘れて、情報の不整合が発生する

n8nなどの自動化ツールもありますが、GitHub Actionsで連携したいと考えました!
この記事では、これらの課題を解決するために実装した、GitHub IssuesとPull RequestsをAsanaタスクと自動連携する仕組みについて紹介していこうと思います!

実装した機能の概要

1. GitHub Issues → Asana タスク自動連携

以下の自動化を実現しました!

Issue作成時

  • Asanaプロジェクトに新しいタスクを自動作成
  • タスク名に[リポジトリ名]のプレフィックスを付けて識別しやすく
  • タスクの説明にGitHub IssueのURLを含める
  • 指定したセクション(例:frontend)に自動配置

Issue状態変化時

  • クローズ時: 対応するAsanaタスクを自動で完了状態にする
  • 再オープン時: Asanaタスクを未完了状態に戻す
  • コメント追加時: Asanaタスクにコメントとして自動追加

2. Pull Request → Asana タスク連携

Pull RequestとAsanaタスクの連携は、Asanaの公式GitHub連携機能を使用して実現しています!
※こちらの設定については、Asanaの公式から提供されている情報を参照してください!

仕組み

AsanaタスクのGitHubフィールドにPull RequestのURLを追加することで、以下の情報が自動的に表示されます:

  • Pull Requestのステータス(Open/Closed/Merged)
  • レビュー状況
  • ビルドステータス
  • 変更行数

使用方法

  1. Asanaタスクを開く
  2. GitHubフィールドを見つける(公式連携を有効にすると表示される)
  3. Pull RequestのURLをコピー
    https://github.com/your-repo/pull/123
    
  4. GitHubフィールドにURLを貼り付ける

すると、Asanaタスク内に自動的にPull Requestの詳細情報がウィジェットとして表示され、リアルタイムでステータスが更新されます!Pull Requestがマージされると、Asana側でもステータスが自動的に「Closed」に変わります。

技術的な実装詳細

システム構成

GitHub Event → GitHub Actions → Node.js Script → Asana API → Asanaタスク

1. Node.jsによるAsana API連携スクリプト

scripts/asana-integration.jsの主要な実装です!

// Asana API にHTTPリクエストを送信する関数
function makeAsanaRequest(method, path, data = null) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: 'app.asana.com',
      port: 443,
      path: `/api/1.0${path}`,
      method: method,
      headers: {
        'Authorization': `Bearer ${ASANA_PAT}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    };

    const req = https.request(options, (res) => {
      let responseData = '';
      
      res.on('data', (chunk) => {
        responseData += chunk;
      });

      res.on('end', () => {
        try {
          const parsedData = JSON.parse(responseData);
          if (res.statusCode >= 200 && res.statusCode < 300) {
            resolve(parsedData);
          } else {
            reject(new Error(`Asana API Error: ${res.statusCode}`));
          }
        } catch (error) {
          reject(new Error(`JSON Parse Error: ${error.message}`));
        }
      });
    });

    req.on('error', reject);
    if (data) {
      req.write(JSON.stringify(data));
    }
    req.end();
  });
}

GitHub Issueからのタスク作成

async function createAsanaTask(issue) {
  const taskData = {
    data: {
      name: `[${repositoryName}] ${issue.title}`,
      notes: `Repository: ${repositoryName}
Issue #${issue.number}

${issue.body || ''}

GitHub Issue: ${issue.html_url}`,
      projects: [ASANA_PROJECT_ID],
      workspace: ASANA_WORKSPACE_ID,
      external: {
        gid: `github_issue_${repositoryName}_${issue.number}`,
        data: issue.html_url
      }
    }
  };

  try {
    const result = await makeAsanaRequest('POST', '/tasks', taskData);
    console.log(`✅ Asanaタスクを作成しました: ${result.data.gid}`);
    return result.data;
  } catch (error) {
    console.error('❌ Asanaタスク作成エラー:', error.message);
    throw error;
  }
}

セクション管理機能

タスクを特定のセクションに自動配置する機能も実装しました:

async function moveTaskToSection(taskId, sectionId) {
  try {
    const result = await makeAsanaRequest('POST', `/sections/${sectionId}/addTask`, {
      data: { task: taskId }
    });
    console.log(`✅ タスクをセクションに移動しました`);
    return result.data;
  } catch (error) {
    console.error('❌ タスク移動エラー:', error.message);
    throw error;
  }
}

2. GitHub Actions ワークフロー

.github/workflows/github-issues-to-asana.yml

name: GitHub Issues to Asana Tasks

on:
  issues:
    types: [opened, closed, reopened]
  issue_comment:
    types: [created]

jobs:
  sync-issues-to-asana:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Sync Issue to Asana with Section Management
        env:
          ASANA_PAT: ${{ secrets.ASANA_PAT }}
          ASANA_WORKSPACE_ID: ${{ secrets.ASANA_WORKSPACE_ID }} 
          ASANA_PROJECT_ID: ${{ secrets.ASANA_PROJECT_ID }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ASANA_SECTION_FRONTEND: "frontend" # リポジトリごとにセクション管理する場合はシークレットで管理することを推奨
        run: node scripts/asana-integration.js
        continue-on-error: true

セットアップ手順

Issues連携のセットアップ

1. Asana Personal Access Token の取得

  1. Asana Developer Console にアクセス
  2. Personal Access Token を作成
  3. 必要な権限を付与(タスクの読み書き、プロジェクトへのアクセス)

2. Workspace ID と Project ID の取得

# Workspace 一覧取得
curl -H "Authorization: Bearer <ASANA_PAT>" \
     https://app.asana.com/api/1.0/workspaces

# プロジェクト一覧取得
curl -H "Authorization: Bearer <ASANA_PAT>" \
     https://app.asana.com/api/1.0/workspaces/<WORKSPACE_ID>/projects

3. GitHub Secrets の設定

GitHubリポジトリの Settings > Secrets and variables > Actions に以下を追加:

  • ASANA_PAT: Asana Personal Access Token
  • ASANA_WORKSPACE_ID: AsanaのワークスペースID
  • ASANA_PROJECT_ID: AsanaのプロジェクトID

4. ファイルの配置

  1. scripts/asana-integration.js を配置
  2. .github/workflows/github-issues-to-asana.yml を配置
  3. リポジトリにプッシュ

Pull Request連携のセットアップ(公式GitHub連携)

1. Asanaプロジェクトで公式連携を有効化

  1. Asanaプロジェクトを開く
  2. 右上の「カスタマイズ」をクリック
  3. 「アプリ」セクションで「+アプリを追加」を選択
  4. 「GitHub」を検索して選択
  5. 「+プロジェクトに追加」をクリック
  6. GitHubアカウントの認証を行う

2. 連携の確認

連携が成功すると:

  • AsanaタスクにGitHubフィールドが追加される
  • Pull RequestのURLを貼り付けるだけで自動的に情報が表示される
  • Pull Requestのステータス変更がリアルタイムで反映される

注意事項

  • この連携はプロジェクト単位で設定する必要がある
  • GitHub Enterprise Serverはサポートされていない
  • 組織のGitHubリポジトリの場合、適切な権限が必要

工夫した点

1. 複数リポジトリ対応

複数のリポジトリから同じAsanaプロジェクトにタスクを作成できるよう、以下の工夫をしました:

  • タスク名に[リポジトリ名]のプレフィックスを付与
  • タスクの説明にリポジトリ名とIssue番号を含める
  • 検索時にリポジトリ名でフィルタリング可能
const taskData = {
  data: {
    name: `[${repositoryName}] ${issue.title}`,
    notes: `Repository: ${repositoryName}\nIssue #${issue.number}\n...`
  }
};

2. エラーハンドリング

GitHub Actionsの実行を止めないよう、continue-on-error: true を設定:

- name: Sync Issue to Asana
  run: node scripts/asana-integration.js
  continue-on-error: true  # エラーが発生してもワークフローを継続

3. デバッグしやすいログ出力

各処理の状態が分かりやすいよう、絵文字付きのログを出力:

console.log(`🚀 GitHub Event: ${eventName}`);
console.log(`✅ Asanaタスクを作成しました: ${result.data.gid}`);
console.log(`❌ Asanaタスク作成エラー: ${error.message}`);

運用してみて分かった効果

  1. タスク管理の二重作業が完全に解消された

    • 開発者はGitHub Issueを作成するだけでOK
    • PMはAsanaでタスクの進捗を確認できる
  2. 情報の不整合がなくなった

    • ステータスが自動同期されるため、常に最新の状態が反映される
    • コメントも自動同期されるため、議論の経緯が両方のツールで確認できる
  3. チーム全体の生産性が向上

    • 手動でのタスク作成・更新作業がなくなり、開発に集中できる
    • PMとエンジニアのコミュニケーションがスムーズに

課題と今後の改善点

現在の課題

1. GitHubユーザーとAsanaユーザーの同期問題

最も大きな課題は、GitHubとAsanaのユーザーアカウントが独立しているため、以下の情報を自動同期できないことです:

  • assigneeの反映不可: GitHub IssueのassigneeをAsanaタスクの担当者として自動設定できない
  • コメント投稿者の識別: GitHubのユーザー名はAsanaに反映されるが、Asanaユーザーとしては認識されない

この問題により、タスクの担当者は手動で設定する必要があり、完全な自動化には至っていません。

解決案の検討

解決案としては以下を検討しており、時間があれば実装する予定です!

  • ユーザーマッピングテーブルの作成(GitHub username ↔ Asana user ID)
  • チームメンバーのメールアドレスを使った自動マッチング
  • Asana APIでユーザー検索を行い、名前ベースでマッチング

その他の改善点

1. 双方向同期の実装

  • 現在はGitHub → Asanaの一方向のみ
  • Asanaでの変更をGitHubに反映する仕組みも検討中

2. カスタムフィールドへの対応

  • Asanaのカスタムフィールド(優先度、期限など)への自動設定
  • GitHub LabelsとAsanaタグの連携

3. より柔軟なセクション管理

  • Issueのラベルに基づいた動的なセクション配置
  • プロジェクトのフェーズに応じた自動移動

まとめ

GitHub IssuesとAsanaタスクの自動連携により、タスク管理の二重作業を完全に解消することができました!
GitHub ActionsとNode.jsを使用することで、比較的シンプルな実装で大きな効果を得ることができます!

同様の課題を抱えているチームの参考になれば幸いです。実装についての質問やフィードバックがありましたら、ぜひコメントでお知らせください!

参考リンク

NonEntropy Tech Blog

Discussion