全社からのデザイン依頼を効率化(Googleフォーム+Slack)
デザインマネージャーとして一番効率化したい仕事。
それは、「見積もり・アサイン依頼」の相談対応。
メインが受託案件の弊社では、各部署ディレクターからのデザイン業務の見積もり・アサイン依頼が日々大量にきます。
テレワークのため、基本SlackのDMでのやり取りでしたが、下手したらその対応に、1/3〜半日かかっている日も。
時間に限りがある中、自身のスキルアップやチームマネジメントの時間を優先的に取るためにも、スキルアップにつながらない見積もり・アサイン業務はなるべく効率化して行きたい🙄
と言うことで、下記2点を実施。
・Googleフォームで依頼+Slackへ自動投稿
(見積もりに関するキャッチボールを減らし、一元管理)
・工数表
(簡易、定型のものは先方で判断できるように)
元々定型にしやすい種類の案件用の見積もりシートは制作していたので、そちらも活用しつつ、上記を追加。
この記事では一点目のGoogleフォームで依頼+Slackへ自動投稿の手順を記載しています。
基本的には、Googleフォーム → Slackへ自動投稿する仕組みなので、他の用途にも流用できるかなと思います。
順番としては以下。
1.Googleフォーム(投稿フォーム作成)
人によっては、見積もりをする上で最低限必要な情報が足りなく、何度も会話のキャッチボールをしないと出てこないなど、個別で考える必要性のあるヒアリングに行き着くまでの無駄なやりとりが発生していました。
なので、見積もりをする際に、何をヒアリングしたいかをリストアップして、項目として埋めていけば解決するようなフォーム項目を設定。
基本必須項目としては、例えば下記のようなもの
・依頼主のSlack名
・案件の担当部署
・案件名
・依頼内容(見積もり作成/確認/アサイン)
・見積もりシートURL(別途あれば)
・返答期限
・区分(新規/既存、サイト・アプリ/SNS系の画像制作なのかなどなど)
・リリース予定日
・デザイン稼働開始予定
制作物の目的や目標、資料や素材、詳細等を尋ねる項目を次のセクションで作っていきます。
ラジオボタン選択による分岐「回答に応じてセクションに移動」機能を使って、依頼する側も最適な項目での回答で済むようにセクションを調整します。
また、こちらの依頼フォームを使うメリットを明示して、先方にも協力してもらう必要があるので、フォームの頭に目的やメリットを記載。
目的
・新人の方が過去の案件で依頼内容を参考にできるため
・見積もり担当者と実際の作業者が変わった際に依頼内容の情報共有が容易
・過去案件の担当者の検索が簡単にできるため
・見積もりアサイン情報の一元化
・依頼窓口を一つにして取りこぼさないようにする
他のメリットとして、ディレクターが何の情報をこちらが欲しているかのすり合わせが事前にできることで、効率よくクライアントからの情報引き出しをできたり、アサイン時にも、アサインするメンバーにスレッドのURLを共有することで仕様に関してのやりとりなどの事前情報もコストなくできます。
2.Slack側の設定
Slackの設定は下記記事を参考にさせてもらいました。
とてもわかりやすいです。
3.Googleフォーム(スクリプト記載)
こちらも上記の記事を参考に設定しました。
ほぼ事足りるかと思うのですが、見積もりアサイン用のチャンネルにたくさん並んでいくので、下記のような表示にするために、少しカスタマイズをしていきます。
改修したいこと
・未記入項目の表示を省く(無駄に長くならないため)
・フォーム項目が多いため、記事の開閉(続きを見るの実装)
・案件名を目立たせて、記事が並んでも、ざっとみた際にわかりやすく
・新規/既存等の区分がすぐにわかるよう左の線色を変える
・未記入項目の表示を省く
・フォーム項目が多いため、記事の開閉
attachmentsにプラスしてBlock Kitを使用。
Block Kit Builderでもう少しリッチなカスタマイズができます。
・案件名を目立たせて、記事が並んでも、ざっとみた際にわかりやすく
開閉前に表示する項目の数制御はできなさそうなので、優先してリスト確認したい箇所を上に表示させます。
・新規/既存等の区分がすぐにわかるよう左の線色を変える
▼全スクリプト
SlackのWebhook URLやチャンネル名の変更及び、Googleフォームで設定した入稿項目に合わせてカスタマイズしてください。
function sendToSlack(fallback, fields, channel) {
const webhookUrl = "SlackのWebhook URL"
var pushNumber = 0;
////////////////////////
//区分を取得
////////////////////////
var kubunText;
var kubunColor;
fields.forEach(function(data) {
if(data.title == '区分') {
kubunText = data.value;
}
});
if ( kubunText.indexOf('新規') != -1) {
kubunColor = '#ffddde';
} else if( kubunText.indexOf('既存') != -1){
kubunColor = '#c7e7fb';
} else {
kubunColor = '#cecece';
}
////////////////////////
//雛形
////////////////////////
var block = [
{
"color": kubunColor,
"blocks": [
{
}
]
}
];
////////////////////////
//出力用関数
////////////////////////
function blockPush(arg){
//タイトルを検索して存在すれば値を代入
var valueText = '';
fields.forEach(function( data) {
if(data.title == arg.title) {
valueText = data.value;
}
});
// valueもdividerもなければ何もしない
if(!(arg.type == 'divider') && valueText == '') return;
//dividerの時
if(arg.type == 'divider'){
block[0]["blocks"][pushNumber] = {};
block[0]["blocks"][pushNumber].type = 'divider';
}
//valueに値が存在する時
if(!(valueText == '')) {
block[0]["blocks"][pushNumber] = {};
if(arg.type) block[0]["blocks"][pushNumber].type = arg.type;
if(!(arg.type)) block[0]["blocks"][pushNumber].type = 'section';
block[0]["blocks"][pushNumber].text = {};
if(arg.text_type) block[0]["blocks"][pushNumber].text["type"] = arg.text_type;
if(!(arg.text_type)) block[0]["blocks"][pushNumber].text["type"] = 'mrkdwn';
//情報によって出力結果を変更する
if(arg.title == '案件名'){
var compText = '- ' +valueText + '\n' + ' ';
} else if(arg.title == '依頼主のSlack名'){
var compText = '*' + arg.title + '*' + '\n' + '```@' + valueText + '```';
} else {
var compText = '*' + arg.title + '*' + '\n' + '```' + valueText + '```';
}
block[0]["blocks"][pushNumber].text["text"] = compText;
if(arg.emoji) block[0]["blocks"][pushNumber].text["emoji"] = true;
}
pushNumber ++;
}
////////////////////////
//ここから出力
////////////////////////
//以下の順番で優先して出力
blockPush({"type": "divider"})//横線
blockPush(
{
'title': '案件名',
'type': 'header',
'text_type': 'plain_text'
}
)
blockPush({'title': '返答期限'})
blockPush({'title': '依頼主のSlack名'})
blockPush({'title': '依頼内容'})
//残りの出力(フォームの順番で出力される)
fields.forEach(function( data) {
var title = data.title;
if (title == '案件名'|| title == '返答期限' || title == '依頼主のSlack名' || title == '依頼内容') return;
blockPush({'title': title})
});
var payload = {
"username": "Googleフォームより依頼", // 1: bot 名,
'attachments' : block,
"icon_emoji": ":envelope_with_arrow:" // 2: アイコン画像
};
var options = {
'method' : 'POST',
'payload': JSON.stringify(payload)
};
//slackに送信
UrlFetchApp.fetch(webhookUrl, options);
}//sendToSlack
function test() {
sendToSlack("テスト通知確認です", [], "#チャンネル名");
}
function responseToText(itemResponse) {
switch (itemResponse.getItem().getType()) {
case FormApp.ItemType.CHECKBOX:
return itemResponse.getResponse().join("\n");
break;
case FormApp.ItemType.GRID:
const gridResponses = itemResponse.getResponse();
return itemResponse.getItem().asGridItem().getRows().map(function (rowName, index) {
Logger.log(rowName);
return rowName + ": " + gridResponses[index];
}).join("\n");
break;
case FormApp.ItemType.CHECKBOX_GRID:
const checkboxGridResponses = itemResponse.getResponse()
return itemResponse.getItem().asCheckboxGridItem().getRows().map(function (rowName, index) {
Logger.log(rowName);
return rowName + ": " + checkboxGridResponses[index];
}).join("\n");
break;
default:
return itemResponse.getResponse();
}
}
function onFormSubmit(e) {
const itemResponses = e.response.getItemResponses();
const fallback = itemResponses.map(function(itemResponse) {
return itemResponse.getItem().getTitle() + ": " + itemResponse.getResponse();
}).join("\n");
const fields = itemResponses.map(function(itemResponse) {
const value = responseToText(itemResponse);
return {
"title": itemResponse.getItem().getTitle(),
"value": value
}
});
sendToSlack(fallback, fields, "#チャンネル名");
}
4.テスト試用してもらい運用・改善
Slack投稿された記事に対しての細かいやりとりは、スレッド内で展開していきます。
こちらのストックが増えていけば、似たような案件で参考にしてもらえたり新人ディレクターがどのように依頼すればいいかの参考になるなど、一石三鳥くらいになるのを期待しています。
そして大事なのは、実際に依頼するメンバーにテスト試用してもらってのフィードバック。
意見をもらって、修正等を反映し、お互いに使いやすく効率化できる仕組みを作っていくことで、より時間をかけたい部分に時間を割ける仕組みを作っていきたいなと思います。
Block Kitに関しての記事があまり見つけられなかったので、誰かの参考になればいいな〜。