その他

作ってみた事例:AsanaのタスクとSalesforceを自動同期する

この記事は「make Advent Calendar 2024」23日目の記事です。

このアドベントカレンダーについて

このアドベントカレンダーは25日間でIPaaS製品の「make」について使い方や、実践を学べる連続ブログ企画です。

おかしん」「ばるす」「たにあん」の3名がリレー形式でお届けします。

25日間のスケジュールは以下の通りです。

日付内容担当
12/1話題のIPaaS製品「make」とはおかしん
12/2makeで作ってみたScenario紹介ばるす
12/3make 基本操作編 機能紹介:Organizationたにあん
12/4make 基本操作編 機能紹介:Scenario、Templateおかしん
12/5make 基本操作編 機能紹介:Connectionsばるす
12/6make 基本操作編 機能紹介:Webhooksたにあん
12/7make 基本操作編 機能紹介:DataStores、DataStructuresおかしん
12/8make 基本操作編 機能紹介:Devicesばるす
12/9make 基本操作編 機能紹介:Functionsたにあん
12/10make 基本操作編 機能紹介:CustomAppsおかしん
12/11make 基本操作編 機能紹介:Flow Control,Tools,Text parserばるす
12/12make ドキュメント動線の話:ResourceHubたにあん
12/13make 検証:Make Bridgeおかしん
12/14make 検証:Make REST APIばるす
12/15make 検証:AI Searchたにあん
12/16make Community Hub:Overviewおかしん
12/17make Community Hub:Academy Courses,Blog Articlesばるす
12/18make Community Hub:Showcase,CustomAppsたにあん
12/19makeの管理運用の話:Github連携おかしん
12/20makeの管理運用の話:実行ログと再実行と停止中リクエスト滞留ばるす
12/21makeの管理運用の話:Connection権限管理たにあん
12/22makeで作ってみた事例:(未定)おかしん
12/23makeで作ってみた事例:(未定)ばるす
12/24makeで作ってみた事例:(未定)たにあん
12/25makeの総論を語るばるす

はじめに

どーもばるすです。

いよいよこのアドベントカレンダーも大詰めですね。

ブログの掲載順が前後しているのは時空のゆがみです。ご容赦ください。

色々とmakeの機能をご紹介してきましたが、今日は実際に弊社で動かしているScenarioの事例について書きます。

処理フロー概要

Scenario全体像

これが動くとどうなるか

Asanaのタスクのステータスを変更すると、Salesforceのレコード情報も同様に変更されます。

フロー詳細:作り方

1.トリガー部分の設定

1-a.makeでWebhookトリガーのScenarioを作成

1-a-1.Webhook作成時、Get request headers にYesを入れる

1-a-2.DataStructureを作成し、Generate をクリック

1-a-3.以下のJsonを貼り付け

RequestJson
{
        "value": "{}",
        "__IMTHEADERS__": [
            {
                "name": "connection",
                "value": "upgrade"
            },
            {
                "name": "x-real-ip",
                "value": "xxx.xxx.xxx.xxx"
            },
            {
                "name": "x-request-id",
                "value": "24fxxxxxxxxxx"
            },
            {
                "name": "content-length",
                "value": "2"
            },
            {
                "name": "cdn-loop",
                "value": "cloudflare; loops=1"
            },
            {
                "name": "cf-ipcountry",
                "value": "US"
            },
            {
                "name": "accept-encoding",
                "value": "gzip, br"
            },
            {
                "name": "cf-ray",
                "value": "8f6cfcf65b5d0a17-IAD"
            },
            {
                "name": "cf-visitor",
                "value": "{\\"scheme\\":\\"https\\"}"
            },
            {
                "name": "content-type",
                "value": "application/json"
            },
            {
                "name": "user-agent",
                "value": "Asana"
            },
            {
                "name": "x-hook-secret",
                "value": "567xxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "cf-connecting-ip",
                "value": "xxx.xxx.xxx.xxx"
            }
        ],
        "__IMTMETHOD__": "POST"
    }

1-a-4.作成するとWebhookURLが生成されるのでこれを控えておく

1-b.Heartbeatを返す処理を追加

1-b-1.WebhookトリガーからWebhook Responseを延ばす

1-b-2.WebhookResponseを設定

Webhookの初回登録時と、登録以降定期的に飛んでくるHeartbeatリクエストへの処理方法は同じです。

3.AsanaにWebhook登録

初回だけなので好みのやり方でOKです。

※Webhook登録にはAsanaのAPI Tokenが必要です。

参考:Personal access token

各パラメータ

リクエストURL:https://app.asana.com/api/1.0/webhooks

リクエストBody

{
  "data": {
    "filters": [
    ],
    "resource": "[task_gid]",
    "target":"[Webhook_URL]"
  }
}

※Filtersの例

{
        "action": "changed",
        "resource_type": "task"
      }

Pattern1:APIリクエストツールでやる場合

Pattern2:makeでリクエストする場合

どちらでもやることは変わりません。お好みで。もちろんGASとかでも可能です。


Establish Webhook API 初回登録時にmakeに飛んでくるリクエスト
{
        "value": "{}",
        "__IMTHEADERS__": [
            {
                "name": "connection",
                "value": "upgrade"
            },
            {
                "name": "x-real-ip",
                "value": "xxx.xxx.xxx.xxx"
            },
            {
                "name": "x-request-id",
                "value": "24fxxxxxxxxxx"
            },
            {
                "name": "content-length",
                "value": "2"
            },
            {
                "name": "cdn-loop",
                "value": "cloudflare; loops=1"
            },
            {
                "name": "cf-ipcountry",
                "value": "US"
            },
            {
                "name": "accept-encoding",
                "value": "gzip, br"
            },
            {
                "name": "cf-ray",
                "value": "8f6cfcf65b5d0a17-IAD"
            },
            {
                "name": "cf-visitor",
                "value": "{\"scheme\":\"https\"}"
            },
            {
                "name": "content-type",
                "value": "application/json"
            },
            {
                "name": "user-agent",
                "value": "Asana"
            },
            {
                "name": "x-hook-secret",
                "value": "567xxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "cf-connecting-ip",
                "value": "xxx.xxx.xxx.xxx"
            }
        ],
        "__IMTMETHOD__": "POST"
    }

2回目以降のリクエスト

[
    {
        "value": {
        "events": [
            {
                "user": {
                    "gid": "119xxxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:02.103Z",
                "action": "removed",
                "resource": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "story",
                    "resource_subtype": "section_changed"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                }
            },
            {
                "user": {
                    "gid": "1199xxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:02.167Z",
                "action": "added",
                "resource": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "story",
                    "resource_subtype": "section_changed"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                }
            },
            {
                "user": {
                    "gid": "1199xxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:01.983Z",
                "action": "added",
                "resource": {
                    "gid": "12090xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "section"
                }
            }
        ]
    },
        "__IMTHEADERS__": [
            {
                "name": "connection",
                "value": "upgrade"
            },
            {
                "name": "x-real-ip",
                "value": "172.68.245.55"
            },
            {
                "name": "x-request-id",
                "value": "2eda1xxxxxxxxxxxxx"
            },
            {
                "name": "content-length",
                "value": "903"
            },
            {
                "name": "cdn-loop",
                "value": "cloudflare; loops=1"
            },
            {
                "name": "cf-ipcountry",
                "value": "US"
            },
            {
                "name": "accept-encoding",
                "value": "gzip, br"
            },
            {
                "name": "cf-ray",
                "value": "xxxxxxxxxxxxx-IAD"
            },
            {
                "name": "cf-visitor",
                "value": "{\"scheme\":\"https\"}"
            },
            {
                "name": "content-type",
                "value": "application/json"
            },
            {
                "name": "user-agent",
                "value": "Asana"
            },
            {
                "name": "x-hook-signature",
                "value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "x-asana-request-signature",
                "value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "cf-connecting-ip",
                "value": "xxx.xxx.xxx.xxx"
            }
        ],
        "__IMTMETHOD__": "POST"
    }
]

2.分岐、Request Body整形処理の追加

2−a.Routerを追加する

2−a−1.JsonParser を追加

!)どこともつながってないっすよってエラーが出るけど気にしない

2−a−2.JsonからWebhookトリガーへ線を延ばす

2−a−3.Routerが追加されるので、赤枠部分の通過条件を入力する

1st の条件:リクエストのHeaderの12個目の要素が’x-hook-secret’である

2ndの条件:リクエストのHeaderの12個目の要素が’x-hook-secret’ではない

2-b.リクエスト内容の判別に使うデータを成形する

2-b-1.Json ParserのData StructureをAdd

2-b-2.Generateをクリックし以下のJsonを追加

貼り付け用Json
[
    {
        "value": {
        "events": [
            {
                "user": {
                    "gid": "119xxxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:02.103Z",
                "action": "removed",
                "resource": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "story",
                    "resource_subtype": "section_changed"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                }
            },
            {
                "user": {
                    "gid": "1199xxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:02.167Z",
                "action": "added",
                "resource": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "story",
                    "resource_subtype": "section_changed"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                }
            },
            {
                "user": {
                    "gid": "1199xxxxxxxxxxxxx",
                    "resource_type": "user"
                },
                "created_at": "2024-12-24T20:56:01.983Z",
                "action": "added",
                "resource": {
                    "gid": "12090xxxxxxxxxxxxx",
                    "resource_type": "task",
                    "resource_subtype": "default_task"
                },
                "parent": {
                    "gid": "1209xxxxxxxxxxxxx",
                    "resource_type": "section"
                }
            }
        ]
    },
        "__IMTHEADERS__": [
            {
                "name": "connection",
                "value": "upgrade"
            },
            {
                "name": "x-real-ip",
                "value": "172.68.245.55"
            },
            {
                "name": "x-request-id",
                "value": "2eda1xxxxxxxxxxxxx"
            },
            {
                "name": "content-length",
                "value": "903"
            },
            {
                "name": "cdn-loop",
                "value": "cloudflare; loops=1"
            },
            {
                "name": "cf-ipcountry",
                "value": "US"
            },
            {
                "name": "accept-encoding",
                "value": "gzip, br"
            },
            {
                "name": "cf-ray",
                "value": "xxxxxxxxxxxxx-IAD"
            },
            {
                "name": "cf-visitor",
                "value": "{\\"scheme\\":\\"https\\"}"
            },
            {
                "name": "content-type",
                "value": "application/json"
            },
            {
                "name": "user-agent",
                "value": "Asana"
            },
            {
                "name": "x-hook-signature",
                "value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "x-asana-request-signature",
                "value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            },
            {
                "name": "cf-connecting-ip",
                "value": "xxx.xxx.xxx.xxx"
            }
        ],
        "__IMTMETHOD__": "POST"
    }
]

2-b-3.Json String にValueを入れる

2-c.WebhookResponse処理を入れる

200を返すだけでOKです。

ここまでで基本的なAsana Webhookの処理は完了です。

テンプレにしとくと楽です。


3.データ更新処理(SAMPLE)

3-a.AsanaのタスクIDを元にSalesforceのレコードを取得

3−b.Asanaのタスク詳細を取得

3-c.取得した内容でSalesforceのレコードを更新

以上で、主なデータ更新系の処理も追加完了です。

トリガー、Webhookの処理が複雑な一方で、データ更新系の処理はシンプルです。


応用例

  • Asanaのタスクが完了されたら、コメント欄を取得してKintoneに記載
  • Asanaのタスクが新規追加されたら、概要欄をAIに記載させる
  • Asanaのタスクが完了されたら、後続のタスクを新規作成して担当者をアサインしSlackに通知

おわりに

以上、AsanaのWebhook APIを用いたデータ更新処理系のScenarioをご紹介しました。長かった…

実はこれ、Workatoでは実現不可なんですよね。
WorkatoってWebhookリクエストへのレスポンスをカスタマイズできないので、AsanaのWebhook APIの初回登録ができないのです。
WorkatoはWorkatoで使いやすいし、やはり使っていて細部まで設計されてて良いなと思うんですけど、Asana Webhook APIについてはWorkatoと相性悪いものでした。
まあiPaaSでWebhookレスポンスをカスタマイズできるものの方が珍しいです。これはmakeがすごい。

iPaaSに限った話ではないのですが、こういう自動化を組むときに一番時間がかかるのはデータの変換処理とかトリガーの設定とかエラーハンドリングなんですよね。
今回の内容からエラーハンドリングは割愛しましたが、以後運用していく場合にはいくつか想定できるエラーのパターンに合わせて、エラー時の処理を追加していく必要があります。

エラーハンドリングについてはまた後日かなー。これだけですごい長くなるから…

ではでは。

ばるす

パチンコ屋→焼き肉屋→情シスを経てクラウドネイティブへ入社。
趣味はギター,キーボード,アウトプット,散歩,読書など。
苦手なものは朝と事務作業。得意分野は眠ること。