今年も秋が短かったですね。こんにちは、デザイナーの池田です。
今回は「デザイナーへの依頼を効率化したいよぉ〜〜」という思いから、地味〜に2022年からやっていた事を世に出して行こうと思います。
さいしょに
デザイナーへ依頼したいな〜〜という時に、弊社では「依頼用のSlackチャンネル」への投稿をお願いしています。
当時は依頼したい人が「@designer」というデザイナー全員にメンションを付けて依頼内容を投稿!というやり方でした。
最初は良かったのですが、依頼された後にこちらが質問するやり取り(例えば締切はいつ?コーディング込み?といったこと)が多く発生するなぁと気づきました。
Slackのワークフローを導入
実際のワークフロー
ということで活用したのがSlackのワークフローです(添付は最新のフォームになります)
これがあれば初手の投稿で知りたいことがわかって効率的!
この内容以外に知りたい内容があれば、投稿された後にスレッドでやり取りをすれば問題なしです。
やりたい事は増えていく
運用していくうちに「もっとこういうことが出来れば便利だよなぁ」と思うようになりました↓
①依頼の一覧性が欲しい
②依頼投稿のスレッドにアサインのお知らせをしたい
スレッドに「やります」や絵文字リアクションで名乗り出ていました
③クローズしたことをスレッドに自動で投稿したい
今までは「完」や「済」の絵文字リアクションをアサインしたデザイナーが手動で押していました
Slackの「リスト」機能を使ってみるがやりたい事が出来ないッ
2024年頃にSlackに「リスト」という機能が追加されました。
ワークフローとの連携も可能だったので、手始めにこの機能を使ってみることに。
依頼用ワークフローのステップを追加し、「リスト」へ自動登録ができた!
だけどリストの項目「担当者」が変更されたとしても、②③の条件である「依頼投稿のスレッド」に対して、特定のメッセージを送信することは残念ながら出来なかったのです…。
SlackAppがあるじゃない
実は2021年からGoogle Apps Script(以下GAS)を利用してSlackAppを作る事を楽しんでいました。
今回もSlackAppを作ってGASと連携させればやりたいことは出来そうだな、ということでやりましょう。
やりたいことはこんな感じ
こういう流れ
Slackのワークフロー
スプレッドシート
Slack Bot
GAS
Asana
上記5つのツールを使用して実現させました。
一覧はAsanaにお任せ
タスク管理としてはAsanaは結構優秀だったので今回の「一覧性」はAsanaを利用させていただきました。
実際のAsana画面
Asanaには「ルール」というものがあり、それがもうめちゃくちゃ便利。
個人的にセクション分けは「アサイン前」「デザイナー1」「デザイナー2」…「完了」としたかったし、ステータスを変えるだけで勝手にセクション移動してくれるのは便利すぎる。
Slackとも連携している点も良い。
Slackで完結しなくなりますが、見た目も好みなのでこちらを採用させていただきました。
少し面倒にはなりますが、やりたいようにやります。
STEP1 依頼投稿されたのをAsanaに登録
準備:スプレッドシートを用意
投稿された依頼を一時保管するスプレッドシートを用意します。
スプレッドシート
1列目には必ず「項目タイトル」を記入してください。
ワークフローで記入してもらった内容をこのスプレッドシートに追加するのですが、1列目のタイトルを参照し、2列目以降から値を入れていくためです。
必要な項目としては以下となります。
TimeStamp(Slackの投稿メッセージを参照するのに必要)
依頼タイトル
期限
デザインorコーディング(Asanaのフィールドを使用している場合)
SlackURL(ワークフローで投稿されたメッセージのURL)
task gid(AsanaのタスクID)
STEP 1-1 Slackワークフロー「チャンネルに投稿」
チャンネルに投稿
依頼用のワークフローを作成します。
ワークフロー作成画面
Slackのサイドメニューに「ツール」があるのでそこから「ワークフロー」を選択し、「新規」から「ワークフローを構築」で上記画像の画面へ遷移します(この遷移の仕方じゃなくても勿論良いです)
入力して欲しい情報を追加していく
「イベントを選択する」を編集し、「Slack内のリンクから開始します」を選択してください。
「その後これらを実行」の「ステップを追加する」が活性化されますので、そこを選択し「情報をフォームで収集する」でワークフローで入力して欲しい内容を作っていきます。
ワークフローのステップ
「ステップを追加」で実行する内容を追加していくのですが、「情報をフォームで収集する」の次のステップでは、必ず一度メッセージを送信 してください。
そうしないと、投稿した「メッセージのURL」が取れないため、タイムスタンプを取ることができなくなります。
STEP 1-2 Slackワークフロー「依頼ストック」
スプレッドシート
フォームの内容を用意したスプレッドシートに追加していきます。
Google Sheetを追加
「ステップを追加」からGoogle Sheetsを選択し、「スプレッドシートに追加する」で追加していきます。
自分自身のGoogleアカウントを紐付け、用意したスプレッドシートを「スプレッドシート」から選択します。
スプレッドシートに追加する値を当てはめる
列部分にはスプレッドシートの1列目に記載したタイトルが表示されるので、該当する項目にフォームで収集した値を当てはめていきます。
当てはめる値はワークフローの前のステップで実行された内容になります。
もちろんワークフローで取れない値は空で問題ありません。
メッセージの送信・リンク
TimeStampの部分にはステップ2で送信された値なので「2.チャンネルへメッセージを送信する」>「メッセージの送信時間」を選択してください。
同様に、SlackURLは「メッセージのリンク」で投稿メッセージに対するURLが取れるようになります。
送信時間の型を選択
「メッセージの送信時間」のドロップダウンをクリックすると、どういう型で追加するかを選択できます。
送信時間は「タイムスタンプ」で設定してください。
実際に入るとこんな感じ
実際にワークフローを動かしてみると、上記のように値が入っていきますいぇ〜〜〜い!
補足:ワークフローで取れる「タイムスタンプ」の桁が足りない!?
タイムスタンプとは、Slackで投稿したメッセージに割り当てられているidのようなものです。
このタイムスタンプを指定することで、「このメッセージのスレッドに投稿してね!」と指示ができるのです。
投稿されたメッセージのURLにある、p以降がタイムスタンプ↓
https://ワークスペース.slack.com/archives/チャンネルID/p1762408808273739
見比べてもらえるとわかるんですが、求めているタイムスタンプが「1762408808273739」に対し、実際にワークフローから取れるタイムスタンプが「1762408808」と桁数が足りない〜!
メッセージを特定するには16桁が必要〜〜!
つまり、「URL後半から取る」か「doPostに届いたBotのリクエストボディ」のどちらかから取らないといけない…。
今回は後者ですが、念の為URLは取っておきます。
Asanaに登録しておけばURLクリックするだけで飛べたりしますし。
STEP 1-3 SlackBot
Bot
フローの要であるBotを作成します。
Bot作成
Slack API: Applications
上記URLにある「Create New App」>「From Scratch」からアプリを作成することができます。
権限付与
「Features」>「OAuth & Permissions」>「Scopes」>「Bot Token Scope」で以下の権限を付与してください。
app_mentions:read
chat:write
Tokenを作成
OAuth Tokensの画面
「Features」>「OAuth & Permissions」>「OAuth Tokens for Your Workspace」で「Install to Workspace」を実行すると、権限リクエスト画面が開くので許可してください。
そうすると先ほどの画面が「OAuth Tokens」に変わるので、発行された「Bot User OAuth Token」をCopyしてください。
GASのプロジェクトを作成
STEP 1-2で使用したスプレッドシートを元に、GASで処理をしAsanaへ登録するステップです。
スプレッドシートの「拡張機能」>「Apps Script」でプロジェクトを開きます。
「無題のプロジェクト」になるので、わかるように命名しておいてください。
最初は「function myFunction()」という関数があると思いますが、そちらは削除して下記サンプルコードに書き換えてください。
const ENV = {
//コピーしたBot User OAuth Tokenを入力↓
BOT_TOKEN: "xxxxxxxxxxxxxxx",
//スプレッドシートのIDを入力(https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaa/)↓
SHEET_ID: "aaaaaaaaaaaaaaaaa"
}
function doPost(e) {
if (!e || !e.postData) {
return ContentService.createTextOutput("no data");
}
// getDataAsString() を使って強制的に文字列化
const raw = Utilities.newBlob(e.postData.contents).getDataAsString("UTF-8");
let data;
try {
if (raw.startsWith("payload=")) {
const decoded = decodeURIComponent(raw.replace("payload=", ""));
data = JSON.parse(decoded);
} else {
data = JSON.parse(raw);
}
} catch (err) {
return ContentService.createTextOutput("parse error");
}
// URL検証対応
if (data.type === "url_verification") {
return ContentService.createTextOutput(data.challenge);
}
// 重複チェック
const eventId = data.event.event_ts;
if (!eventId) return ContentService.createTextOutput("no event id");
const processed = PropertiesService.getScriptProperties().getProperty(eventId);
if (processed) return ContentService.createTextOutput("ok"); // 既に処理済み
PropertiesService.getScriptProperties().setProperty(eventId, "done");
/* -----------
ここにやる事追加していくっぞ!
-------------- */
return ContentService.createTextOutput("ok");
}
デプロイ
右上にある「デプロイ」から「新しいデプロイ」を選択し、「種類の選択」横のギアアイコンから「ウェブアプリ」を選択します。
GASのデプロイ画面
説明:自分が管理しやすい説明文で大丈夫です(未入力の場合は無題になります)
次のユーザーとして実行:自分
アクセスできるユーザー:全員
上記設定でデプロイをします(権限についてのダイアログが出たら許可をしてください)
ウェブアプリURLをコピー
デプロイが完了するとウェブアプリのURLが発行されます。
このURLをコピーして、Slack apiの先ほどの画面に戻ります。
GASへのリクエストURLをSlackアプリに設定
「Features」>「Event Subscriptions」>「Enable Event」をONにし、「Request URL」に先ほどコピーしたURLを貼り付けます。
Request URL横に「Verified 」と表示されれば成功です。
成功したら下にある「Save Changes」で保存。
これでBotの作成は完了です。
ワークフローを使用するチャンネルに /invite @dg-asana-NEKO などでアプリを追加してもらえればオッケーです。
ワークフローにBotのメンションを追加
依頼用のワークフローのステップとして、Botにメンションをさせます。
Botにメンションをすることで、GASのイベントを発火させるためです。
ステップ追加
ステップ4としてスレッドにメンション付きのメッセージを送信します。
ステップ2として追加した投稿にメンションをつけると、まだスプレッドシートに内容が追加されていないため、イベントが発火しても上手くいかないので、必ずスプレッドシートに追加した後で呼んでください。
全て終わったら「公開」を押してワークフローの作成は終了です。
STEP 1-4 GAS
GAS
さて、次はスプレッドシートに追加された内容を元に、Asanaへタスクとして登録させるためにゴリゴリコードを書いていきます。
function doPost(e) {
if (!e || !e.postData) {
return ContentService.createTextOutput("no data");
}
const raw = Utilities.newBlob(e.postData.contents).getDataAsString("UTF-8");
let data;
try {
if (raw.startsWith("payload=")) {
const decoded = decodeURIComponent(raw.replace("payload=", ""));
data = JSON.parse(decoded);
} else {
data = JSON.parse(raw);
}
} catch (err) {
return ContentService.createTextOutput("parse error");
}
if (data.type === "url_verification") {
return ContentService.createTextOutput(data.challenge);
}
const eventId = data.event.event_ts;
if (!eventId) return ContentService.createTextOutput("no event id");
const processed = PropertiesService.getScriptProperties().getProperty(eventId);
if (processed) return ContentService.createTextOutput("ok"); // 既に処理済み
PropertiesService.getScriptProperties().setProperty(eventId, "done");
/* ----------- 追加 -------------- */
//@dg-asana-NEKOがメンションされたスレッドの親のタイムスタンプを取得↓
//実際のタイムスタンプは「1234567890.123456」のように、10桁と6桁の間に「.」がくる形
var timeStamp = data.event.thread_ts || data.event.ts;
//Asanaにタスクを追加するイベント↓
taskTsuikasuru(timeStamp);
/* ----------- 追加 -------------- */
return ContentService.createTextOutput("ok");
}
まず「doPost」に追加 の部分を追加してください。
doPostはGASのWebアプリがPOSTリクエストを受け取ったときに自動的に呼ばれる特別な関数です。
スプレッドシートのどのタスクを追加するかを指定する
Asanaにタスクを追加するための準備コードです。
function taskTsuikasuru(timeStamp) {
var iraiList = getIraiList();//スプレッドシートに記載があるセルを全取得
var sheet = getSheet();//スプレッドシートを取得
for(var i = 0; i <= iraiList.length - 1; i++) {
//スプレッドシート1列目「TimeStamp」を、doPostで取得した親メッセージのタイムスタンプと照らし合わせ↓
var item = iraiList[i][0];
if(item === String(Math.floor(timeStamp))){
//正しい16桁のタイムスタンプに書き換える↓
sheet.getRange(i+1, 1).setValue(timeStamp);
//Asana APIを使ってタスクを作成するイベント(後ほど解説)
createAsanaTask(i);
}
}
}
//スプレッドシートを取得
function getSheet() {
var sheet = SpreadsheetApp.openById(ENV.SHEET_ID);
var sheetTab = sheet.getSheetByName('依頼');//スプレッドシートのシート名を指定
return sheetTab
}
//スプレッドシートに記載があるセルを全て選択する
//.getRange(行番号, 列番号, 行数, 列数)
function getIraiList() {
var sheet = getSheet()
var last = sheet.getLastRow();
// ↓列数「8」は実際に使用している列数を入れてください
const iValues = sheet.getRange(1, 1, last, 8).getValues();
return iValues
}
taskTsuikasuru() でdoPostから受け取った「1234567890.123456」といったようなタイムスタンプを、スプレッドシートの1列目のワークフローで取得した「1234567890」のタイムスタンプと照らし合わせて「この行のタスクをAsanaへ追加して〜」と言えるようにしておきます。
前述の通りタイムスタンプの桁数が違うので、ごにょごにょと整数にして比較し、正しいものへと置き換えます。
getSheetとgetIraiListですが、スプレッドシートの内容を取得したり書き換えたりするには、都度「どのスプレッドシート?」「列はどこまで?」など指定しないといけないので、何回も同じ事を書かずにすむようにしています。
Asana API利用の準備
Asanaへ追加させるための処理を書く前に、Asana APIを利用するための準備をします。
Asana Tokenの取得
まず開発者コンソール へアクセス。
「個人アクセストークン」>「トークンを新規作成」でAPIを利用するためのトークンを発行していきます。
トークンを新規作成
トークン名は任意ですが、あとで見てわかるように書けば良いと思います。
「Asana API 利用規約に同意します」をチェックし、「トークンを作成」でトークンが作成されます。
その後、「トークンをコピーしてください」といったダイアログが表示されるのでコピーをしてください。
モーダルを閉じた後にはコピーができないのでご注意を!
GASに戻り、最初に記載した「ENV」に「ASANA_TOKEN」を追加します。
const ENV = {
BOT_TOKEN: "xxxxxxxxxxxxxxx",
SHEET_ID: "aaaaaaaaaaaaaaaaa",
// コピーしたトークンを追加↓
ASANA_TOKEN: "cccccccccccccccccccccc"
}
ワークスペースIDを取得
次にタスクを追加したいAsanaのワークスペースIDを取得します。
Asanaにログインをした状態で下記URLにアクセスすると、ワークスペースの情報がでます。
そこに記載されている「gid」がワークスペースIDです。
https://app.asana.com/api/1.0/workspaces
{"data":[{
"gid":"oooooooooooooo", ←これ
"resource_type":"workspace",
"name":"ワークスペースの名称"
}]}
コピーしたワークスペースIDを「ENV」に追加してください。
const ENV = {
BOT_TOKEN: "xxxxxxxxxxxxxxx",
SHEET_ID: "aaaaaaaaaaaaaaaaa",
ASANA_TOKEN: "cccccccccccccccccccccc",
// コピーしたワークスペースIDを追加↓
ASANA_WORKSPACE_GID: "oooooooooooooo"
}
プロジェクトIDを取得
次にプロジェクトIDを取得します。
プロジェクトIDは、タスクを追加させたいAsanaのプロジェクトを開きます。
登録されているタスク、もしくはプロジェクトのURLに記載があります↓
https://app.asana.com/1/****************/project/プロジェクトID/list/****************
project/ の後に続く16桁の数字がプロジェクトIDです。
それをコピーしておいてください。
これで準備は完了です!
GASからAsanaへタスクを追加する
さて、いよいよGASからAsanaへとタスクを追加させます。
function createAsanaTask(row) {
// スプレッドシートを取得する
var iraiList = getIraiList();
var sheet = getSheet();
// 今日の日付を取得
var today = dayjs.dayjs();
// スプレッドシートの4列目の値「期限」を取得↓(0から始まるので実際の列-1)
var dueOn = iraiList[row][3];
// スプレッドシートの5列目の値「デザインorコーディング」を取得↓(0から始まるので実際の列-1)
var scope = iraiList[row][4];
// カスタムフィールドIDを指定する(「デザイン」「コーディング」「デザインとコーディング」)
var customFieldsID;
if (scope.indexOf('と') >= 0) {
customFieldsID = "****************"; // 「デザインとコーディング」のカスタムフィールドID
} else if (scope.indexOf('デザイン') >= 0) {
customFieldsID = "****************"; // 「デザイン」のカスタムフィールドID
} else {
customFieldsID = "****************"; // 「コーディング」のカスタムフィールドID
}
// Asanaへ送信する情報をここで一回まとめています
var payload = {
"data": {
"workspace": ENV.ASANA_WORKSPACE_GID,
// 先ほどコピーしたプロジェクトID↓
"projects": "プロジェクトID",
// タスクのタイトルになります。
// スプレッドシートの3列目「依頼」が入ります。
"name": iraiList[row][2],
// ワークフローでは開始日を指定していないので、一旦登録した日付(今日)を入れます
"start_on": today.format('YYYY-MM-DD'),
// 期限が入ります
"due_on": dayjs.dayjs(dueOn).format('YYYY-MM-DD'),
// カスタムフィールドを指定します
"custom_fields": {16桁のプロジェクトID: customFieldsID},
"notes": "Asanaのタスク詳細「説明」に入る内容になります。"
}
};
var options = {
"method" : "post",
"headers" : {
"Authorization" : "Bearer " + ENV.ASANA_TOKEN //Bearerの後ろのスペース消さないこと!
},
"contentType": "application/json",
"payload" : JSON.stringify(payload)
};
var url = "https://app.asana.com/api/1.0/tasks";
var response = UrlFetchApp.fetch(url, options);
var result = JSON.parse(response);
// Asanaに追加されたら「タスクID」が発行されるので、スプレッドシートの8列目に追加します
var taskGid = result.data.gid;
sheet.getRange(row+1, 8).setValue(taskGid);
}
dayjs
ライブラリからdayjsを追加する
GASのライブラリである「dayjs」ですが、日付操作を楽〜にしてくれる優れものです。
GASの左側にある「ライブラリ」から上記のダイアログを開き、下記コードを入力して検索し、特段とくに変更することなくそのまま追加してください。
1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB
カスタムフィールド
Asanaのカスタムフィールドについてはこちら をご確認ください。
事前に登録してあるカスタムフィールドIDを指定することで、設定された状態で登録が可能です。
今までのようにWebから確認する方法がないため、下記ブログ等をご参考に取得してください。
technicalsoumu.com
payloadの説明
workspace:ワークスペースID
projects:プロジェクトID
name:タスクの名前
start_on:タスク開始日(YYYY-MM-DD)
due_on:タスク終了日(YYYY-MM-DD)
custom_fields:カスタムフィールド
notes:説明
上記の内容でAsanaへ登録します。
taskGid
「response」 で送信した内容をJSONの形にしたものを「result」に入れています。
その結果には登録されたタスクの「タスクID」が含まれているので、スプレッドシートに追加します。
その後のステップに必要です。
以上で「STEP1 依頼投稿されたのをAsanaに登録したい」は終了となります。
STEP2 アサインした事をスレッドに投稿したい
Asanaで「担当者」が追加された時に、依頼メッセージのスレッドに「担当者がつきました」という内容を投稿させます。
STEP2-1 AsanaはAsana上で担当者を追加させるだけなので、特にやることはありません。
STEP2-2 Slackワークフロー「アサイン用」
STEP2-2 Slackワークフロー「アサイン用」
Slackのワークフローで「アサイン用」を作成します。
トリガーを指定
トリガーを追加
ワークフローが開始されるトリガーを、「Asana」>「ワークスペースのタスクの担当者が更新された時」とします。
「ワークフロー実行用アカウント」を聞かれるので、ご自身のアカウントと連携させてください。
ワークスペースとプロジェクトを指定
Asanaのワークスペースとプロジェクトを指定してください。
担当者は未設定で大丈夫です。
チャンネルへ投稿
チャンネルへメッセージを送信する
このワークフローを依頼が投稿された#依頼チャンネル ではなく、#デザイナー のチャンネルへ投稿させます。
そこでSTEP1で作成したBotにメンションをさせ、GASのイベントを発火させるのです。
投稿メッセージを編集
投稿させるチャンネルを指定し、メッセージに以下を追加させます。
Botのメンション:先頭においてください
アサイン用とわかる文言
タスクID:「変数を挿入する」>「タスク」>「ID」で追加できます
タスク名:「変数を挿入する」>「タスク」>「名前」で追加できます
担当者:「変数を挿入する」>「タスク」>「担当者(Slackユーザー)」で追加できます
担当者は表示させる型が選択できますが、@浅田で大丈夫です。
あとは公開をすればAsana上で担当者が更新されるたびにこのワークフローが動いてくれます。
実際に投稿させたメッセージはこんな感じ
STEP2-3 GAS
アサインされた処理を書いていこう
#デザイナー に投稿されたメッセージをトリガーにした処理を一気に書いていきましょう。
const ENV = {
BOT_TOKEN: "xxxxxxxxxxxxxxx",
SHEET_ID: "aaaaaaaaaaaaaaaaa",
ASANA_TOKEN: "cccccccccccccccccccccc",
ASANA_WORKSPACE_GID: "oooooooooooooo"
//#依頼チャンネルのチャンネルIDを追加↓
CHANNEL_ID: "********"
}
function doPost(e) {
if (!e || !e.postData) {
return ContentService.createTextOutput("no data");
}
const raw = Utilities.newBlob(e.postData.contents).getDataAsString("UTF-8");
let data;
try {
if (raw.startsWith("payload=")) {
const decoded = decodeURIComponent(raw.replace("payload=", ""));
data = JSON.parse(decoded);
} else {
data = JSON.parse(raw);
}
} catch (err) {
return ContentService.createTextOutput("parse error");
}
if (data.type === "url_verification") {
return ContentService.createTextOutput(data.challenge);
}
const eventId = data.event.event_ts;
if (!eventId) return ContentService.createTextOutput("no event id");
const processed = PropertiesService.getScriptProperties().getProperty(eventId);
if (processed) return ContentService.createTextOutput("ok"); // 既に処理済み
PropertiesService.getScriptProperties().setProperty(eventId, "done");
/* ----------- 追記 -------------- */
var timeStamp = data.event.thread_ts || data.event.ts;
// data.event.text:Botが受け取った投稿のメッセージ
// replaceでメンションを取った状態に整形↓
var get_text = data.event.text.replace(/<@BotのメンバーID> /g ,"");
var taskID, userID;
// 「アサイン用とわかる文言」と合致させる
if (get_text.indexOf('担当者がつきました') >= 0) {
// メッセージから「タスクID」を取るためのイベント
taskID = taskIDToru(get_text);
// メッセージから「担当者」を取るためのイベント
userID = userIDToru(get_text);
// 担当者を、依頼メッセージのスレッドに投稿させるイベント
assignOshieru(taskID, userID);
} else {
taskTsuikasuru(timeStamp);
}
/* ----------- 追記 -------------- */
return ContentService.createTextOutput("ok");
}
function taskIDToru(get_text) {
// タスクIDが「(1234567890123456)」のように()で囲われているので、それを基準にして16桁の数字のみ抜き出しています
var cut1 = get_text.substr(0, get_text.indexOf(')'));
var taskID = cut1.substr(cut1.indexOf('(') + 1);
return taskID;
}
function userIDToru(get_text) {
// メッセージの中にある「@」が担当者になるので、それを基準に担当者名を抜き出しています
var cut2 = get_text.substr(get_text.indexOf('@') + 1);
var userID = cut2.substr(0, cut2.indexOf('>'));
return userID;
}
function assignOshieru(taskID, userID) {
// userIDToruから抜き出したユーザーIDを投稿メッセージとして指定します
var message = `<@${userID}> がアサインしました :yosi:`;
// 依頼メッセージのタイムスタンプを取得します
var ts = slackMessageDore(taskID);
if (ts !== false) {
// Slackへ投稿させるためのイベントです
postMessageToSlack(ts, message);
}
}
function slackMessageDore(taskID) {
// taskIDToruから抜き出したAsanaのタスクIDを基準にして、スプレッドシートの依頼を探します
var iraiList = getIraiList();
for(var i = 0; i <= iraiList.length - 1; i++) {
// スプレッドシートの8列目にあるタスクIDと、taskIDToruで抜き出したIDを比較させます
if(iraiList[i][7] === taskID){
// スプレッドシートの1列目にあるtimestampを取得させて返します
var ts = iraiList[i][0];
return ts;
}
}
return false;
}
function postMessageToSlack(ts, message) {
// Slackに投稿します
var payload = {
channel: ENV.CHANNEL_ID, //依頼チャンネル
thread_ts: ts, // 投稿メッセージのスレッドへ投稿されます
text: message, // 投稿メッセージ
};
var options = {
method: "post",
contentType: "application/json",
headers: { 'Authorization': 'Bearer ' + ENV.BOT_TOKEN },
payload: JSON.stringify(payload),
};
// SlackAPIにリクエストを送信
var apiUrl = "https://slack.com/api/chat.postMessage";
var response = UrlFetchApp.fetch(apiUrl, options);
}
チャンネルID
依頼メッセージがあるチャンネルのIDです。
該当チャンネルの右上にある「…(その他)」>「チャンネル詳細を開く」で「チャンネル情報」の一番下にあります。
そちらをコピーして貼ってください。
BotのメンバーID
アプリの詳細
Slackのサイドメニュー「App」から作成したBotを追加させ、DMを開きます。
右上にある「…(その他)」>「アプリの詳細を開く」で「チャンネル情報」の「設定」下にある「メンバーID」です。
デプロイ
GASをデプロイして反映させましょう。
デプロイを管理
「デプロイ」>「デプロイを管理」を開きます。
実際のデプロイ管理です
左の「アクティブ」が選択された状態で鉛筆アイコンの「編集」を選択します。
そうすると、「バージョン」と「説明」が活性化されるので、「バージョン」>「新バージョン」を選択します。
「説明」はそのバージョンの説明文をわかるように書いたら、ひとつ前の状態のバージョンでデプロイし直したい場合などに便利です。
STEP1-3でやった「新しいデプロイ」からデプロイしてしまうと、SlackAPIで指定したウェブアプリのURLが変更されてしまうので、修正や追記する場合は必ず「デプロイを管理」からデプロイしてください。
デプロイが完了したら、これでSTEP1〜STEP2が完了しました。
実際の投稿メッセージ
STEP3 クローズした事をスレッドに投稿したい
これも実はSTEP2とやっていることは一緒です。
ワークフローのトリガーにする条件を「ワークスペースのタスクが完了した時」に設定をさせた「完了用」のワークフローを作成すればオッケーです。
あとはdoPostに「完了とわかるキーワード」をif文で分岐させ、完了処理をさせれば完了です。
function doPost(e) {
if (!e || !e.postData) {
return ContentService.createTextOutput("no data");
}
const raw = Utilities.newBlob(e.postData.contents).getDataAsString("UTF-8");
let data;
try {
if (raw.startsWith("payload=")) {
const decoded = decodeURIComponent(raw.replace("payload=", ""));
data = JSON.parse(decoded);
} else {
data = JSON.parse(raw);
}
} catch (err) {
return ContentService.createTextOutput("parse error");
}
if (data.type === "url_verification") {
return ContentService.createTextOutput(data.challenge);
}
const eventId = data.event.event_ts;
if (!eventId) return ContentService.createTextOutput("no event id");
const processed = PropertiesService.getScriptProperties().getProperty(eventId);
if (processed) return ContentService.createTextOutput("ok"); // 既に処理済み
PropertiesService.getScriptProperties().setProperty(eventId, "done");
var timeStamp = data.event.thread_ts || data.event.ts;
var get_text = data.event.text.replace(/<@BotのメンバーID> /g ,"");
var taskID, userID;
if (get_text.indexOf('担当者がつきました') >= 0) {
taskID = taskIDToru(get_text);
userID = userIDToru(get_text);
assignOshieru(taskID, userID);
/* ----------- 追記 -------------- */
} else if (get_text.indexOf('タスクが完了しました') >= 0) {
taskID = taskIDToru(get_text);
taskEnd(taskID);
} else {
taskTsuikasuru(timeStamp);
}
/* ----------- 追記 -------------- */
return ContentService.createTextOutput("ok");
}
function taskEnd(taskID) {
var message = `こちらの依頼はクローズになりました :hapica:`;
var ts = slackMessageDore(taskID);
if (ts !== false) {
postMessageToSlack(ts, message);
kanryouKesu(taskID);
}
}
function kanryouKesu(taskID) {
// 完了ステータスをスプレッドシートから削除する
var iraiList = getIraiList();
var sheet = getSheet();
for(var i = 0; i <= iraiList.length - 1; i++) {
if(iraiList[i][7] === taskID){
sheet.deleteRow(i+1);
}
}
return null;
}
補足:SlackワークフローからAsanaのタスクを登録しなかった理由
STEP2で、ワークフローでAsanaが使用できることを紹介しました。
トリガーとして紹介しましたが、ステップでもAsanaを利用することができます。
そこに「タスクを作成する」という項目があるんですが、今回は以下の理由でこちらを使用しませんでした。
ワークフローを実行する人も、Asanaアカウントが必要になる
期日に入れる値は、ワークフローでは「日時」を選択していないとダメだった
デザイナーに依頼をする社内メンバー全員がAsanaのアカウントを保持しているわけではないので、このワークフローをそもそも使用できなくなります。
上記だけで選択肢から外しているんですが、さらに期日が「日時」でないとダメなようでした。
ワークフローの情報で入力する「締め切り」を「日付」で対応したかったので、フォーマットが合わずに断念しました(時間まで細かく設定して欲しいわけじゃないので…)
さいごに
すごい長くなりましたが、弊社のデザイナーへの依頼フローはこのようにして構築しております。
もう少し簡単なやり方もあるのかもしれませんが、自分が納得する形としては現状これで良いなと思っております。
Slackのリストがもう少し自分好みにカスタマイズできたらSlackで完結させられるかな〜とも思っています。
以上、GASを活用したSlack-Asana間のデザイン依頼フロー構築についてでした。
ありがとうございました。