hero_picture
Cover Image for Slackワークフロー + GAS + Notion API で承認フローを作ってみました(multi_select でタグ登録)

Slackワークフロー + GAS + Notion API で承認フローを作ってみました(multi_select でタグ登録)

こんにちは。システム開発事業部のタナカです。

今回は作業効率化のために、SlackワークフローからNotionにデータを登録する方法を試してみましたのでご紹介します。

目次

経緯

シーズでは、業務で使用するアプリケーションは ゆるい許可制 となっていて、所定のGoogleスプレッドシートに必要事項を記入し、Slackの申請用チャンネルで審査を依頼するという運用となっています。

そして審査後、審査結果はスプレッドシートに記入され、Slackにてその連絡が返ってくるのですが、GoogleスプレッドシートとSlackのどちらにも記入するのは手間だなと常々思っていました。

はじめは、ナレッジツールとして社内で使用しているNotionだけで運用しようかと考えたのですが、やはり連絡はSlackの方が使いやすいので、まずはSlackからNotionにデータを登録する方法を調べてみました。

Send to Notion

https://www.notion.so/integrations/slack

最初に見つけたのが、SlackからNotionのデータベースへ直接データを登録することができる「Send to Notion」という機能。

Slackから /notion create コマンドを実行することでSlackワークフローのようなモーダルウィンドウが起動するのですが、Notionのデータベース名を選択してから、プロパティを選んで内容を記入するというもので、Slackワークフローのように予め入力項目を指定しておくことができないようでした。

今回の目的は承認フローなので、Notionへの登録だけでなく、Slackでの承認依頼も絡めたものにしたい。。。

Google Apps Script ( GAS )

https://workspace.google.co.jp/intl/ja/products/apps-script/

そこで、SlackワークフローからNotionにデータを登録する方法を調べてみたところ、Google Apps Script (GAS) を使うことで、ノンプログラマのわたしでもできそうなものを見つけたので試してみることにしたという経緯です。

参考サイト

以下のサイトを参考にさせていただきました。

NotionのAPIを使ってSlack上のフロー情報をストックする仕組みを作ってみた | クラッソーネ開発者ブログhttps://tech.crassone.jp/posts/stock-information-from-slack-to-notion

Slackワークフロー✖️Notion API✖️GASでSlackからNotionに未確認の依頼をリスト化するhttps://zenn.dev/rescuenow/articles/324e3b34a933f5

したいこと

  • アプリケーションの使用申請フローの実現
  • Slackワークフローを使用
  • Notoinのデータベースに登録
  • 通知等はSlack

申請フロー(概要)

  1. Slackワークフローにて必要項目を記入して申請
  2. 申請内容がNotionに登録される(Slackにも申請内容を投稿)
  3. Slackにて担当者への通知とNotionへのリンクが届く
  4. 審査後、担当者がNotionにて審査結果を記入
  5. Slackにて審査結果が投稿される

作業手順(概要)

💡
手順の詳細は、上記の参考サイトで詳しく紹介されているため、大まかな流れのみとなっています。

Notion

  1. Notionに登録するデータベースを作成(プロパティは日本語でも可)
  2. Notionのインテグレーションを作成
  3. Notionデータベースにインテグレーションを追加して連携

Googleスプレッドシート

  1. SlackからNotionに経由するためにスプレッドシートを作成
  2. 作成したスプレッドシートの1行目に列を識別するカラム名を入力

Google Apps Script(GAS)

  1. 作成したスプレッドシートにApps Scriptを作成
  2. 作成したApps Scriptのトリガーを設定

Slack

  1. ワークフローを作成
  2. 必要なステップを追加
  3. ワークフローを公開

サンプルコード(GAS)

申請フロー用に作成したNotionデータベースのプロパティ

この画像の内、GASで取得しているのは以下の10項目です。

  • 申請日 [type : date]
  • アプリ名 [type : title]
  • URL [type : url]
  • コスト [type : select]
  • タイプ [type : select]
  • タグ [type : multi_select]
  • 申請理由 [type : rich_text]
  • リスク [type : select]
  • 判断条件 [type : rich_text]
  • 申請者 [type : rich_text]
  • 今回作成したプロパティについての補足事項

    • 「作成者」にはNotionのインテグレーション名が入ってしまうため、別途「申請者」を作成しています。
    • 「ステータス」は基本 新規申請 となるので、Googleスプレッドシートに固定で入力されるよう、Slackワークフロー側で設定しています。
    • 過去にスプレッドシートで運用していたリストも、Notionに移行するため、自動で作成されてしまう「作成日時」とは別に、後から編集できるよう「申請日」を作成しています。
    • タグ」は、Notionに登録された後、使用可能なアプリ一覧のデータベースとして検索する際に活用できるように用意しました。「,(カンマ)」区切りで複数登録できるようにしています。

    申請日のサンプルコード

    本日の日付を「-(ハイフン)」区切りで用意して

    1const date = new Date();
    2const yyyy = date.getFullYear();
    3const mm = ("0" + (date.getMonth() + 1)).slice(-2);
    4const dd = ("0" + date.getDate()).slice(-2);
    5const today = yyyy + "-" + mm + "-" + dd;

    Notion API に渡します。

    1"申請日": {
    2  "type": "date",
    3  "date": {
    4    "start": today
    5  }
    6},

    タグのサンプルコード(multi_select)

    「,(カンマ)」で分割して配列にして

    1const appTagString = appTag;
    2const appTagArray = appTagString.split(",");
    3
    4var appTagMultiSelect = [];
    5var appTagArrayLength = appTagArray.length;
    6for (var i = 0; i < appTagArrayLength; i++) {
    7  appTagMultiSelect.push({'name': appTagArray[i]});
    8}

    Notion API に渡します。

    1"タグ": {
    2  "multi_select": appTagMultiSelect
    3},

    bookmark のサンプルコード

    なくても良かったのですが、登録したNotionページに、URLを bookmark で埋め込みをしてみました。

    1"children": [
    2  {
    3    "object": "block",
    4    "type": "bookmark",
    5    "bookmark": {
    6      "url": appUrl,
    7      "caption": [
    8        {
    9          "type": "text",
    10          "text": {
    11            "content": appName
    12          }
    13        }
    14      ]
    15    }
    16  } 
    17]

    完成したサンプルコード

    1function RequestWishListApp() {
    2
    3  const wishListAppSheet = SpreadsheetApp.getActiveSheet();
    4  const latestEntryRow = wishListAppSheet.getLastRow();
    5  const applicationDate = wishListAppSheet.getRange(latestEntryRow, 1).getValue();
    6  const appName = wishListAppSheet.getRange(latestEntryRow, 2).getValue();
    7  const appUrl = wishListAppSheet.getRange(latestEntryRow, 3).getValue();
    8  const appCost = wishListAppSheet.getRange(latestEntryRow, 4).getValue();
    9  const appType = wishListAppSheet.getRange(latestEntryRow, 5).getValue();
    10  const appTag = wishListAppSheet.getRange(latestEntryRow, 6).getValue();
    11  const applicationReason = wishListAppSheet.getRange(latestEntryRow, 7).getValue();
    12  const appRisk = wishListAppSheet.getRange(latestEntryRow, 8).getValue();
    13  const appSelfJudge = wishListAppSheet.getRange(latestEntryRow, 9).getValue();
    14  const appApplicant = wishListAppSheet.getRange(latestEntryRow, 10).getValue();
    15
    16  const payload = addWishListApp(applicationDate, appName, appUrl, appCost, appType, appTag, applicationReason, appRisk, appSelfJudge, appApplicant);
    17  postNotion(payload);
    18
    19}
    20
    21
    22function postNotion(payload){
    23
    24  const MY_NOTION_TOKEN = "Notionのトークン";
    25  const url = "https://api.notion.com/v1/pages";
    26  const options = {
    27    "method": "post",
    28    "headers": {
    29      "Content-type": "application/json",
    30      "Authorization": "Bearer " + MY_NOTION_TOKEN,
    31      "Notion-Version": "2022-06-28",
    32    },
    33    "payload": JSON.stringify(payload)
    34  };
    35  UrlFetchApp.fetch(url, options);
    36}
    37
    38
    39function addWishListApp(applicationDate, appName, appUrl, appCost, appType, appTag, applicationReason, appRisk, appSelfJudge, appApplicant){
    40  const DATABASE_ID = "NotionデータベースのID";
    41
    42  const date = new Date();
    43  const yyyy = date.getFullYear();
    44  const mm = ("0" + (date.getMonth() + 1)).slice(-2);
    45  const dd = ("0" + date.getDate()).slice(-2);
    46  const today = yyyy + "-" + mm + "-" + dd;
    47
    48  const appTagString = appTag;
    49  const appTagArray = appTagString.split(",");
    50
    51  var appTagMultiSelect = [];
    52  var appTagArrayLength = appTagArray.length;
    53  for (var i = 0; i < appTagArrayLength; i++) {
    54    appTagMultiSelect.push({'name': appTagArray[i]});
    55  }
    56
    57
    58
    59  payload = {
    60    "parent": {
    61      "type": "database_id",
    62      "database_id": DATABASE_ID
    63    },
    64    "properties": {
    65      "申請日": {
    66        "type": "date",
    67        "date": {
    68          "start": today
    69        }
    70      },
    71      "アプリ名": {
    72        "title": [
    73          {
    74            "text": {
    75              "content": appName
    76            }
    77          }
    78        ]
    79      },
    80      "URL": {
    81        "url": appUrl
    82      },
    83      "コスト": {
    84        "select": {
    85          "name": appCost
    86        }
    87      },
    88      "タイプ": {
    89        "select": {
    90          "name": appType
    91        }
    92      },
    93      "タグ": {
    94        "multi_select": appTagMultiSelect
    95      },
    96      "申請理由": {
    97        "rich_text": [
    98          {
    99            "text": {
    100              "content": applicationReason
    101            }
    102          }
    103        ]
    104      },
    105      "リスク": {
    106        "select": {
    107          "name": appRisk
    108        }
    109      },
    110      "判断条件": {
    111        "rich_text": [
    112          {
    113            "text": {
    114              "content": appSelfJudge
    115            }
    116          }
    117        ]
    118      },
    119      "申請者": {
    120        "rich_text": [
    121          {
    122            "text": {
    123              "content": appApplicant
    124            }
    125          }
    126        ]
    127      }
    128    },
    129
    130    "children": [
    131      {
    132        "object": "block",
    133        "type": "bookmark",
    134        "bookmark": {
    135          "url": appUrl,
    136          "caption": [
    137            {
    138              "type": "text",
    139              "text": {
    140                "content": appName
    141              }
    142            }
    143          ]
    144        }
    145      }
    146      
    147    ]
    148
    149
    150  }
    151  return payload;
    152
    153}

    最後に

    この申請フローのリリース後に早速活用されているのを見て、思い切って常々手間だと思っていたことの改善に取り組んでみて良かったと感じています。

    今回は小さなカイゼンでしたが、これを機に他にもカイゼンを積み重ねて行きたいと思います!

    おまけ

    記事内で使用しているNotion風のイラストは Notion Avatar Maker を使用させていただきました。

    CC0 license で利用可能です。

    https://notion-avatar.vercel.app/
    https://notion-avatar.vercel.app/