新機能、アプリのホーム・ヴューを活用しよう🏡

By Tomomi Imura

Published: 2019-11-22

先日おこなわれた TinySpec Osaka & Tokyo でお約束した通り、日本語の App Home チュートリアルを書きました。 App Home Demo -Stickies

今回新しくリリースされた機能、App Home は、ユーザと Slack を1対1で繋ぐことができるスペースで、さらにユーザに直感的にアプリを使ってもらうために加えられた機能なのです。App Home には3つのタブがあり、アプリについての情報をみるための About、チャット対話式ボット機能がある場合にそのボットとダイレクトメッセージで会話できる Messages、そして今回新しく Home タブが加わりました。

この Home タブは、アプリと各ユーザの間を繋ぐプライベート・スペースで、動的で継続的なコンテンツを提供できるビジュアル・インターフェイスなのです。

App Home って前からあったよね?

もしかしてすでに、ユーザがアプリを開いた時に発生するイベント、 app_home_opened をご存知かもしれません。以前に紹介した Bolt JavaScript framework チュートリアルの Hello world, Bolt⚡️ Bolt フレームワークを使って Slack Bot を作ろう でも触れられています。今回はさらにそれがバージョンアップして、 Home タブとともに、メッセージだけではなく Block Kit を使って動的なコンテンツを表示するとこが可能になりました。

App Home タブ活用例

Google カレンダーの App Home タブ活用例を見てみましょう。 App Home in Google Calendar ユーザは Home タブから毎日のカレンダーを見ることができるだけではなく、予定への返答を変更したり、Zoom などのカンファレンス・コールへのジョインなどもここから行うことができるのです。 他、ミーティングなどの予定のある1分前に通知をする機能もついているのです。グーグル、グッドジョブ👍

🔨 App Home を実装してみよう

さて、この App Home をどうやって使うことができるのか、一緒にアプリを制作しながら試してみましょう。ここでは、ユーザがちょっとしたメモをとることができる Stickies(付箋)アプリを作ってみます。

ユーザー・フロー

ここでのユーザー・フローは下の通りになります。

  1. ユーザが Slackクライアントの左メニューからアプリ名をクリック
  2. アプリの Home タブが開く
  3. ユーザが Home ビュー上にある Add a stickie (メモの追加)ボタンをクリック
  4. モーダルが表示されるので、ユーザがメモ内容を入力し Create(作成)をクリック
  5. Home が自動的に更新され、入力内容が ビューに反映される

App Home user flow GIF

アプリケーション・フロー

ここでのアプリ側のフローは次の通りになります。

  1. ユーザが App Home をオープンすることにより app_home_opened イベントが発生し、アプリのサーバに情報が送信される
  2. アプリは、そのイベント・ペイロードから user ID を抽出し views.publish メソッドによって、ボタン UI のある初期画面を表示
  3. ユーザがその “Add a Stickie” ボタンをクリックすることにより、そのインタラクションがトリガーされる
  4. アプリは views.open メソッドで入力フォーム UI のあるモーダルを表示
  5. ユーザがそのフォーム内容を送信することによって、view_submission のついたもうひとつのインタラクションがトリガーされる 
  6. views.publish メソッドで App Home ビュー表示を更新

Diagram

さて、大まかなフローがわかったところで実際に Stickies アプリを作ってみましょう。 Glitch というサービス上にソースコードを置いてありますので "remix" してみましょう(リミックスとは GitHub の fork のような物、と思ってください)。このサービスではウェブ上でコードのエディットと実行ができますので、デプロイは不要なのでそのまま動かしてみましょう。

🎏🥫 Glitch でソースコードだけ覗いてみる 🎏🍴 Remix (fork) して自分で使う

⚙️ アプリのセットアップ

まずは Slack アプリの設定をしなければなりません。 Slack App マネージメント でアプリの作成をします。ポップアップが出ますのでそこでアプリの名称を設定し、アプリのインストール可の(=自分がアドミン権限を持つ)ワークスペースを選択してください。ない場合は新規でワークスペースを作成してから始めましょう。

アプリの初期設定ができましたら、次は Features > Bot User 設定画面でボット・ユーザを有効にしてください。そのままデフォルト、もしくは任意の名前を決めてください。

今度は Features > OAuth & Permissions で Bot トークンのスコープを追加します。chat.write を選択してください。 (実際にはこのサンプルアプリにはチャットの機能がありませんので、このスコープを使う必要がないのですが、とりあえず追加してください! この最近追加された、以前より細やかになった権限設定については OAuth 2.0, Version 2 ドキュメントを参照してください。)

次に Features > App Home (下のスクリーンショットの、ステップ1参照)へ行ってください。このフィーチュアは 2019年11月現在ではまだベータですので、ベータ版を使うためにサインアップ(ステップ2)をしてもらう必要があります。 app_home_beta.png サインアップをしましたら、Home タブが使えるようになります。このサンプルアプリではダイレクトメッセージは使用しませんが、Message タブはそのまま有効のままでも良いでしょう。

次に Features > Event Subscription 画面へ行き、イベントを有効にしてください。(下のスクリーンショットのステップ1参照)。そして Request URL (ステップ 2) を入力します。Glitch 上のコードをリミックスして作業を進めている方は、 Request URL は https://プロジェクト名.glitch.me/slack/events のようになります。 (Glitch は新規でプロジェクトが作成される毎にプロジェクト名を自動生成します。 たいがいはハイフンで繋いだ2つの英単語からランダムに生成されるので、 fluffy-umbrella のようなものになっていると思います。自分でカスタムのプロジェクト名もつけられますので、その際はそのカスタム名を使ってください。自分のサーバを使用している際は、そのURL + /slack/events にします。)

Request URL 入力が終わったら、Subscribe to bot events までスクロールし、 app_home_opened イベントを加えてください (ステップ 3)。そして緑の Save Changes ボタンで保存します。(ステップ 4). events.png

次は同じように Features > Interactive Components で Slack サーバにインタラクティブ・ペイロードの送信先を伝える必要があります。 ここでも Request URL を指定してください。URL は https://プロジェクト名.glitch.me/slack/actions となります。そしてここでも Save Changes ボタンで変更を保存してください。

ここで一度インストールしましょう。 Features > OAuth & Permissions からインストールボタンで自分のワークスペースにこのアプリをインストールします。画面の指定にしたがってインストールしてください。一旦 OAuth を使ってのインストールプロセスが終了しましたら、次の場面でアクセス・トークンが発行されます。

さて、次はブラウザ上で使えるIDEの Glitch プロジェクト、またはお使いのコード・エディタに移りましょう。インストール終了時に発行された xoxb- トークンをコピーし、環境変数ファイル、.envSLACK_BOT_TOKENの値としてペーストしてください。 Glitch .env

もう一つの環境変数である、 Signing Secret キーは、Slack App マネージメント画面の Settings > Basic information から取得することができます。

🖼 App Home を表示させよう

Express server で Node アプリをセットアップ

このチュートリアルでは、Node.js と Express サーバを使ってアプリを書いています。ここでは全ての API コールは、ごく一般的な HTTP リクエスト、レスポンスで行っていますので、Node 以外の言語をお使いの皆さんにも、ソースコードをみてもらえればどこでどのように API を呼び出しているかわかっていただけるかと思います。

⚡️ もっと簡単に Bolt フレームワークで書きたい!という方のためにも Bolt で書かれたソースコードも用意しています。しかしこのチュートリアルの説明そのものは Bolt や SDK、他のボット・フレームワークを使わない「バニラ」コードでの説明となります。

まず Node コードで、依存モジュールを追加して Express サーバを走らせます。そして raw リクエスト・ペイロードを使ってリクエスト情報の認証をします。詳しい説明は割愛しますが、index.js の 31 - 38 行目を参照してください。 143 -145 行目では Express サーバを実行しています。

リクエスト情報の認証についてもう少し詳しく知りたい方は、以前書いたチュートリアルの Slack メッセージ・アクション API を使ってディスカバラブルなアプリを作ろう の中のセクション、🔐 リクエスト情報の認証)を読んでみてください。

app_home_opened イベント・ペイロードを受け取るアプリケーション・エンドポイント

次に HTTP POST のルーティングメソッドを使って、イベントペイロードを受け取るエンドポイントを定義します(前のステップで設定した、Request URL)。何かのイベントが発生した際にはこのエンドポイントに Slack API からの JSON ペイロード情報が届くので、そこでイベントが app_home_opened であるかチェックし、そうであれば App Home ヴューを表示させる準備をします。

app_diagram_app_home_opend.png

ここでは、読みやすさを考え、簡略化されたコード・スニペットを使っていますが、全てみたい方は、 index.js 45 - 70 行目)をみてください:

app.post('/slack/events', async(req, res) => {
  const {type, user, channel, tab, text, subtype} = req.body.event;

  if(type === 'app_home_opened') {
    displayHome(user);
  }
}

さて、ビューの表示には、リッチなコンテンツを Block Kit を使って構築しましょう:

const displayHome = async(user, data) => {

  const args = {
    token: process.env.SLACK_BOT_TOKEN,
    user_id: user,
    view: await updateView(user)
  };
  const result = await axios.post('/views.publish', qs.stringify(args));
};

Home App にコンテンツを表示させるには view.publish メソッドを使います。 ここでは axios モジュールをを使って HTTP POST 経由で API を呼び出しています。

Block Kit でリッチ・コンテンツを構築

ここでの例では、別の関数、updateView を呼んでコンテンツを JSON で構築しています。この関数は Home ビュー UI を更新する毎に呼びだしています。

初期 UI の設定はこのようになります:

const updateView = async(user) => {
    let blocks = [ 
    {
      // Section with text and a button
      type: "section",
      text: {
        type: "mrkdwn",
        text: "*Welcome!* \nThis is a home for Stickers app. You can add small notes here!"
      },
      accessory: {
        type: "button",
        action_id: "add_note", 
        text: {
          type: "plain_text",
          text: "Add a Stickie"
        }
      }
    },
    // Horizontal divider line 
    {
      type: "divider"
    }
  ];

  let view = {
    type: 'home',
    title: {
      type: 'plain_text',
      text: 'Keep notes!'
    },
    blocks: blocks
  }

  return JSON.stringify(view);
};

blocks 配列部分は、Block Kit をつかったプロトタイプはこちらから見ることができます

実際のコードでは、この関数では、モーダルからユーザ入力された値を使い動的なコンテンツを扱います。この部分は後ほど。

ユーザのボタンクリックのハンドリング

ユーザがボタンをクリックするとモーダルが開きます。 app_diagram_button_click.png

Block Kit メッセージ・ビルディングブロックには action_id を定義してください。これはデータを受け取る識別子となります。(ここでは add_note)。

ユーザがボタンをクリックすると API サーバから Request URL に送信されるこのアクションについてのペイロードには trigger_id が含まれます。これはモーダルを開くための識別子となります。

app.post('/slack/actions', async(req, res) => {
  const { token, trigger_id, user, actions, type } = JSON.parse(req.body.payload);
  if(actions && actions[0].action_id.match(/add_/)) {
    openModal(trigger_id);
  } 
});

モーダルを開く

次に入力フォーム・エレメントを、モーダルの中に表示しましょう。この例ではごくシンプルに、付箋にメモするテキストを入力するマルチライン(複数行)インプット・ボックスと、付箋の色を指定するドロップダウン・メニューを表示させます。

モーダルを表示させるには views.open メソッドを呼びます:

const openModal = async(trigger_id) => {

  const modal = {
    type: 'modal',
    title: {
      type: 'plain_text',
      text: 'Create a stickie note'
    },
    submit: {
      type: 'plain_text',
      text: 'Create'
    },
    blocks: [
      // Text input
      {
        "type": "input",
        "block_id": "note01",
        "label": {
          "type": "plain_text",
          "text": "Note"
        },
        "element": {
          "action_id": "content",
          "type": "plain_text_input",
          "placeholder": {
            "type": "plain_text",
            "text": "Take a note... "
          },
          "multiline": true
        }
      },

      // Drop-down menu      
      {
        "type": "input",
        "block_id": "note02",
        "label": {
          "type": "plain_text",
          "text": "Color",
        },
        "element": {
          "type": "static_select",
          "action_id": "color",
          "options": [
            {
              "text": {
                "type": "plain_text",
                "text": "yellow"
              },
              "value": "yellow"
            },
            {
              "text": {
                "type": "plain_text",
                "text": "blue"
              },
              "value": "blue"
            }
          ]
        }
      }
    ]
  };

  const args = {
    token: process.env.SLACK_BOT_TOKEN,
    trigger_id: trigger_id,
    view: JSON.stringify(modal)
  };

  const result = await axios.post('/views.open', qs.stringify(args));
};

一見長いコードですが、ほとんどは JSON で UI を描いているだけです。 Block Kit プロトタイプはここから見ることができます。

フォーム・サブミッションのハンドリング

ユーザからのフォーム・サブミッションのハンドリングは、ボタンクリックのハンドリングと同じようにすることができます。 app_diagram_modal_submit.png

モーダル内のフォームがサブミットされた際には、このアクションのエンドポイントにペイロードが届きます。ボタンクリック時と区別するにはペイロードに含まれる type の値をチェックしてください:

app.post('/slack/actions', async(req, res) => {
  const { type, user, view } = JSON.parse(req.body.payload);
  else if(type === 'view_submission') {
    res.send(''); // Make sure to respond to the server to avoid an error

    const data = {
      note: view.state.values.note01.content.value,
      color: view.state.values.note02.color.selected_option.value
    }
    displayHome(user.id, data);
  }
});

この部分のコード全てを見るには index.js 107 - 133 行を参照してください。

App Home ビューの更新

ここでユーザから追加されたデータを元のデータに追加して、 Home タブの内容を views.publish メソッドと使って書き換えます。

このサンプルコードでは、単純化するために永続データの格納に node-json-db モジュールを使用しています。ユーザが新しいメモを追加する毎に、そのデータが下のデータ配列にプッシュされていきます。

その新しいデータを、元の JSON にアペンドしてできた新たな UI ブロックを、 views.publish メソットで再表示しています。

実際のソースコードは appHome.js の 17 - 152 行をみてみてください。このあたりは自分の好きなように処理してみてください。

アプリを実行してみよう

さて、これで一通りアプリが動くはずですので試してみましょう。Slack クライアントのサイドメニューの App でみられるアプリ一覧からこの Stickies! アプリを探してクリックしてみてください。無事、App Home が表示されましたか?

Add a Stickie ボタンを押して、新しい付箋が Home ビューに反映されたか確認してみてください。🎉 tada_it_works.png

よりよいユーザ・エクスプリエンスを確立するには

さて、このチュートリアルが、新しいアプリを作る、もしくは既存のアプリをアップデートするためのインスピレーションになっていただけたらうれしいです。

ここでは views メソッドを用いた基本的な App Home 構築について書きましたが、次のチュートリアルでは、Shay DeWael (シェイ・デワエル)がベスト・プラクティスについて書いてゆきますので乞うご期待!

📄 Related Slack API Documentation


ご質問などがありましたら、私、 Tomomi @girlie_mac@SlackAPIへ日本語もしくは英語でツイートするか、feedback@slack.com に連絡ください!

原文(英語): Building a home for your app 🏡 by Tomomi Imura (Slack)