Multi-Session Livebook Apps で GitHub と Slack を連携させる

## はじめに

Livebook 0.10.0 がリリースされましたね！

https://news.livebook.dev/whats-new-in-livebook-0.10---introducing-multi-session-livebook-apps-3Dbpss

というわけで、公式の動画で Jose がやっていた Multi-Session Livebook Apps の構築をやってみました

https://www.youtube.com/watch?v=dSjryA1iFng&t=1640s

:::note info
基本的に公式動画に沿って実装するだけなので、英語が聞ける人は動画を見た方が良いです

また、公式動画では説明のためにわざと遠回りしていたところを端折って説明します

GitHub トークンや Slack トークンについては手順を紹介するので、そこで詰まった人には参考になると思います
:::

## Multi-Session Livebook Apps とは

Livebook Apps はノートブック上に実装した処理を、そのままアプリとしてリリースできる機能です

https://qiita.com/RyoWakabayashi/items/9cd0032d1bd4ddad5d6c

これによって、例えば以下のような画像識別AIアプリなどが簡単に構築できます

https://qiita.com/RyoWakabayashi/items/5ac91260b4d6b55adc1a

![deploy_ai.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1584f3c3-5ef1-0ea2-890b-0ea9cb38110b.gif)

Livebook 0.9 の Livebook Apps は Single-Session だけでした

Single-Session では複数人がアプリにアクセスしたとき、全員で一つのセッションを共有します

セッションを共有することでチャットや共同編集など、チームでのコラボレーション機能を構築しやすくなっています

それに対して、新しい Multi-Session では、新しくアプリにアクセスしてきた人は、既存のセッションに参加するか、新しいセッションを作るか選択できます

別々のセッションを作った場合、それぞれに対する操作は完全に分離されます

例えば「CSV ファイルを読み込んで、指定した列の値をグラフ化する」という処理を Livebook Apps を実装したとき、セッション毎に別の CSV ファイル、列を指定することができます

## アプリの実装内容

GitHub 上の指定したリポジトリーの情報を取得し、 Slack に取得結果を通知するアプリを作っていきます

![slack.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/14fa38d8-636f-ad4f-8941-baff76f72871.gif)

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/multi_session_app.livemd

## 事前準備

### Livebook のインストール

@nako_sleep_9h さんの記事などを参考に Livebook をインストール、起動します

https://qiita.com/nako_sleep_9h/items/585786648d2b7560d010

Livebook のバージョンは 0.10.0 以上が必須なので、既にインストールしている人は最新バージョンに更新してください

### トークンの取得

GitHub と Slack の API を呼ぶため、それぞれの認証トークンが必要になります

#### GitHub のトークン

GitHub にログインします（アカウントがない場合はサインアップしてください）

https://github.com/

GitHub の右上、自分をアイコンをクリックし、表示されるメニューから "Settings" を開きます

![スクリーンショット 2023-07-16 8.31.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/41d61589-bccb-dd31-023f-6473bc96c6d5.png)

"Settings" 画面で左メニューの一番下、 "Developer Settings" を開きます

![スクリーンショット 2023-07-16 8.33.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1acb9e33-f275-1747-9282-142e004f392a.png)

"Developer Settings" 画面の左メニュー "Personal access tokens" |> "Fine-grained personal access tokens" を開き、右上の "Generate new token"

![スクリーンショット 2023-07-16 8.36.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/9a08c378-f48d-e717-cb86-2b8188f7e1a9.png)

:::note info
"Fine-grained personal access tokens" は従来の "Tokens (classic)" よりも、より詳細に権限管理できるようになっています

まだ Beta 版ではありますが、 GitHub のトークンを使う場合、今後はこちらを使いましょう
:::

今回は特別な操作はしないため、 "Token name" に適当な値を入れたら他はそのまま "Generate token" をクリックします

![スクリーンショット 2023-07-16 8.39.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/0e631b91-1573-a2fa-6fbd-e811bf4ca213.png)

新しいトークンが発行され、値が表示されます

#### Slack のトークン

slack api にログインします（Slack のアカウントがない場合、サインアップして何らかのワークスペースを作ってください）

https://api.slack.com/

右上 "Your apps" を開き、 "Create your first app" をクリックします（初めての Slack アプリの場合）

![スクリーンショット 2023-07-16 8.45.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/37afa0a1-7c2e-a5f3-59a6-c47d9620fbb7.png)

"Your Apps" 画面で "Create an App" をクリックします

![スクリーンショット 2023-07-16 8.47.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/931f7c2f-31de-4c90-d480-f643f2fcb68a.png)

開いたモーダルで "From scratch" をクリックします

![スクリーンショット 2023-07-16 8.47.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c65fcd41-7097-efdb-cd8b-eb1a45878e9d.png)

"App Name" に適当な名前を入力し、使いたいワークスペースを選択します

"Create App" をクリックすると、 Slack アプリが作成されます

![スクリーンショット 2023-07-16 8.47.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/56043069-dada-1104-0391-c37d6847eb77.png)

左メニューから "OAuth & Permissions" を開きます

![スクリーンショット 2023-07-16 8.52.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8ff92bc6-cfe9-cf88-12e8-e78d2c1ae6a8.png)

下に移動し、 "Scopes" で上側の "Bot Token Scopes" に "Add an OAuth Scope" から "chat:write" の権限を追加します

![スクリーンショット 2023-07-16 8.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/64a6b699-1ee9-b0ec-0b9c-9502baca5ebe.png)

画面上に戻ると、 "Install to Workspace" が活性化しているので、クリックします

![スクリーンショット 2023-07-16 8.56.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f367e210-3a91-25b2-4722-24183b454023.png)

"許可する" をクリックします

![スクリーンショット 2023-07-16 8.56.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a4827ed6-3603-6cb5-cf78-eba2307c661c.png)

Slack のトークンが表示されます

Slack でワークスペースを開くと、作成したアプリが追加されています

![スクリーンショット 2023-07-16 8.58.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d3d9b588-7e41-a153-0677-e5ae3c6ce87f.png)

### Slack チャンネルの設定

お試し用のチャンネルを作ります

Slack で "チャンネルを追加する" |> "新しいチャンネルを作成する" をクリックします

![スクリーンショット 2023-07-16 8.59.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/eedd15f3-73d7-6c44-06fd-645573325087.png)
 
適当なチャンネル名を入力し "次へ" をクリックします

![スクリーンショット 2023-07-16 8.59.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8e9ab640-70d5-46cc-f294-cb48f54cc964.png)

何も変更せずに "作成" をクリックします

![スクリーンショット 2023-07-16 9.00.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7efaba14-aabd-cd59-c8c8-7093a882cb0d.png)

新しいチャンネルが作成され、 "メンバーを追加する" というモーダルが開きますが、誰も追加しないので "後でする" をクリックします

![スクリーンショット 2023-07-16 9.00.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/fc7e6b90-c69c-6189-4f16-4fd3f950d334.png)

左メニューのチャンネル名を右クリックし、 "チャンネル詳細を表示する" をクリックします

![スクリーンショット 2023-07-16 9.01.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/fd04a0b2-58cb-149c-4eee-5f218105f6bb.png)

表示されるモーダルの "インテグレーション" タブを開き、 "App" の中の "アプリを追加する" をクリックします

![スクリーンショット 2023-07-16 9.01.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e8bf89de-dc07-2526-8d76-f9da59a386c1.png)

表示されるアプリ一覧から、自分が作成したアプリの右側 "追加"　をクリックします

![スクリーンショット 2023-07-16 9.01.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/023f206d-b7ec-0a26-9821-cf92cedf0626.png)

これで準備は完了です

## アプリの実装

### セットアップ

新しいノートブックを開き、 Multi-Session Livebook App を構築してみましょう

以下のコードをセットアップセルに入力し、実行します

```elixir
Mix.install([
  {:req, "~> 0.3"},
  {:kino, "~> 0.10"},
  {:kino_slack, "~> 0.1"}
])
```

必要なモジュールがノートブック上にインストールされます

### シークレットの登録

GitHub と Slack のトークンをシークレットとして登録します

Livebook 左メニューの錠前アイコンをクリックし、 "SECRETS" を開きます

"+ New secret" をクリックします

![スクリーンショット 2023-07-16 9.13.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/72bc10a4-9478-9047-fff8-d78702e29761.png)

Name を "GITHUB_TOKEN" 、 Value を発行したトークンの値にし、 "in :house: My Hub" を選択して "+ Add" をクリックします

![スクリーンショット 2023-07-16 9.16.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/6b79a588-1668-3a4a-9734-f2065b10742b.png)

"only this session" の場合、ノートブック上ではシークレットを読み込めますが、デプロイしたアプリからは読み込めなくなります

Slack のトークンについても Name を "SLACK_APP" にして同様に登録します

### GitHub API の準備

```elixir
gh =
  Req.new(
    base_url: "https://api.github.com",
    auth: {:bearer, System.fetch_env!("LB_GITHUB_TOKEN")},
    headers: [
      {"Accept", "application/vnd.github+json"},
      {"X-GitHub-Api-Version", "2022-11-28"}
    ]
  )

Kino.nothing()
```

[Req](https://github.com/wojtekmach/req) を使って GitHub API にアクセスするため、クライアントを作成します

先程登録したシークレットは名前の先頭に "LB_" が付いて環境変数になっています

`System.fetch_env!("LB_GITHUB_TOKEN")` で、 GitHub トークンを取得し、 API の認証情報に設定しています

それ以外の設定は GitHub API の公式からコピーしています

https://docs.github.com/ja/rest/security-advisories/repository-advisories?apiVersion=2022-11-28#list-repository-security-advisories

最終行の `Kino.nothing()` は作成したクライアントの中身をユーザーに見せなくするために入れています

## GitHub API の呼び出し

```elixir
repo =
  "オーナー/リポジトリー"
  |> Kino.Input.text()
  |> Kino.render()
  |> Kino.Input.read()

if not String.contains?(repo, "/") do
  Kino.interrupt!(:normal, "オーナー/リポジトリー を入力してください")
end

report =
  case Req.get!(gh, url: "/repos/#{repo}/security-advisories") do
    %{status: 200, body: body} ->
      "リポジトリー #{repo} には #{length(body)} 個の報告があります"

    %{status: 404} ->
      Kino.interrupt!(:error, "リポジトリー #{repo} は存在しません")

    %{status: status} ->
      Kino.interrupt!(:error, "GitHub はステータス #{status} でエラーを返しました")
  end
```

GitHub API の "/repos/<オーナー>/<リポジトリー>/security-advisories" にアクセスし、セキュリティ報告を取得しています

今回は実際の報告結果ではなく、単に `length(body)` を表示しているので、結果は常に 1 になります

実行すると、以下のようなフォームが表示されます

![スクリーンショット 2023-07-16 9.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d0110f3a-35fd-4b1d-abd5-8f49bff45b81.png)

`Kino.Input.text` によってテキスト入力を作成し、正しく入力されていない場合（"/"を含んでいない場合）は `Kino.interrupt` で先に進まないよう制御しています

テキスト入力に `livebook-dev/livebook` と入力し、 Continue をクリックします

![スクリーンショット 2023-07-16 9.43.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1e1c2187-bb79-b864-2b94-a417f5f27618.png)

### Slack へのメッセージ送信

```elixir
req =
  Req.new(
    base_url: "https://slack.com/api",
    auth: {:bearer, System.fetch_env!("LB_SLACK_TOKEN")}
  )

channel =
  "Slack Channel"
  |> Kino.Input.text()
  |> Kino.render()
  |> Kino.Input.read()

if not String.starts_with?(channel, "#") do
  Kino.interrupt!(:normal, "送信先チャンネルを指定してください")
end

response =
  Req.post!(req,
    url: "/chat.postMessage",
    json: %{channel: channel, text: "新しい報告が来ました
#{report}"}
  )

case response.body do
  %{"ok" => true} ->
    "報告が送信されました！"

  %{"ok" => false, "error" => error} ->
    Kino.interrupt!(:error, "Slack 送信でエラーが発生しました #{error}")
end
```

GitHub と同じく環境変数 "LB_SLACK_TOKEN" から Slack のトークンを取得し、 Slack API を呼び出します

![スクリーンショット 2023-07-16 9.49.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d5ba18bc-87cc-662a-5374-2ba51f986e25.png)

Slack Channel に用意しておいたチャンネル名を入力し、 Continue をクリックします

![スクリーンショット 2023-07-16 9.50.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3325b25b-b8db-6667-2375-fb0c8da6a286.png)

Slack にメッセージが来ました

![スクリーンショット 2023-07-16 9.51.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c5ff24ee-4da2-2204-a36e-f882b6237d73.png)

## アプリのデプロイ

ここまでで処理の実装は完了です

あとはこれを Multi-Session Livebook App としてデプロイします

### デプロイの設定、実行

Livebook の左メニューロケットアイコンから "APP" メニューを開き、 "Configure" をクリックします

![スクリーンショット 2023-07-16 9.52.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/59f35a92-3e4c-ac94-d2cb-bc4563d60c68.png)

"Slug" (アプリをデプロイしたときの URL の末尾) に適当な値を入れます

"Session type" は "Multi-session" を選択します

"Deploy" をクリックします

![スクリーンショット 2023-07-16 9.54.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d8d9118c-c4b3-c80f-5b90-80bbd5eb9f53.png)

"APP" メニューにデプロイメントが追加されました

![スクリーンショット 2023-07-16 9.56.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1de6c209-bd2b-d740-bc0e-84bda65f8823.png)

### セッションの開始

http://localhost:8080/apps/multi を新しいタブで開きましょう

セッションの確認モーダルが表示されるので、 "+ New session" をクリックします

![スクリーンショット 2023-07-16 9.57.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/72da7060-1156-2f8d-7e4b-ce51f2c6932c.png)

ノートブックからコードが除去され、入力フォームだけが残った画面が開きました

![スクリーンショット 2023-07-16 10.00.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f5baaad5-061e-b08b-570b-3ca20a9c8620.png)

"オーナー/リポジトリー" に "livebook-dev/livebook" を入力し、 "Continue" をクリックします

![スクリーンショット 2023-07-16 10.02.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/32d54312-58b3-e42c-d683-9ef8c1793489.png)

"Slack Channel" にチャンネル名を入力し、 "Continue" をクリックします

![スクリーンショット 2023-07-16 10.03.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/90223bc3-fcb9-ed75-d84a-d1de5aa56a40.png)

![スクリーンショット 2023-07-16 10.03.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/0c1d50a6-b3ae-037f-92c4-615a282aaa58.png)

ちゃんと Slack までメッセージが送信されました

### 新しいセッションの開始

http://localhost:8080/apps/multi をもう一つの新しいタブで開きましょう

すると、セッションの確認モーダルが表示され、既に動いているセッションが表示されます

![スクリーンショット 2023-07-16 10.04.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/9e35f6ec-089e-67bb-e281-d19f939cc62f.png)

"+ New session" をクリックすれば、新しいセッションが開始され、再び初期状態からアプリを操作できます

![スクリーンショット 2023-07-16 10.00.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f5baaad5-061e-b08b-570b-3ca20a9c8620.png)

http://localhost:8080/apps/multi を更に新しいタブで開きましょう

![スクリーンショット 2023-07-16 10.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c4508343-537e-992f-28ce-5d5300fafe77.png)

既に開いている二つのセッションが表示されています

下の方のセッションをクリックします

![スクリーンショット 2023-07-16 10.03.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/90223bc3-fcb9-ed75-d84a-d1de5aa56a40.png)

この場合、既に実行済の状態でアプリが開きます

これは最初に開いていたタブと同じセッションなので、こちらのタブでリポジトリーを変更すると、その内容は最初のタブにも連動します

Livebook の "APP" メニューを見ると、二つのセッションが実行中になっていることが分かります

![スクリーンショット 2023-07-16 10.11.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e9a72e7e-77b1-620d-4cdb-c5066345a0eb.png)

Livebook トップの "Apps" メニュー http://localhost:8080/apps を開くと、二つのセッションにそれぞれ何人が接続しているかも分かります

![スクリーンショット 2023-07-16 10.13.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/dee2c927-cd8b-eb86-5ca8-15db451bdb20.png)

## 再デプロイ

Livebook の APP メニューから Configure を開き、 "List existing sessions" のチェックを外して "Deploy" をクリックします

![スクリーンショット 2023-07-16 12.11.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5398fcdd-766b-c3d6-1987-fc2ed7c83cf1.png)

この状態でアプリを開くと、既存のセッションが表示されず、新しいセッションを開くことしかできなくなります

![スクリーンショット 2023-07-16 9.57.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/72da7060-1156-2f8d-7e4b-ce51f2c6932c.png)

デプロイする度にバージョン番号が増えていきますが、古いバージョンにアクセスしていたセッションはその時点のバージョンのままです

![スクリーンショット 2023-07-16 12.14.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a50add7e-b7b2-533e-d7c6-dea4e53e5118.png)

##　既存セッションへの操作

"APP" メニューから各セッションを操作できます

![スクリーンショット 2023-07-16 12.17.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/86c61b03-0e3c-22d3-da3b-dd407f06b2d6.png)

左下リンク :link: アイコンを開くと、当該セッションに参加できます

右下の左側ターミナルアイコンを開くと、デバッグモードでアプリが開きます

デバッグモードではアプリにコードが表示され、通常のノートブックとして編集、操作できます

![スクリーンショット 2023-07-16 12.20.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/52d52cf9-a64e-4516-ed6f-f9198b91e355.png)

注意しないといけないのは、あくまでも当該セッションに対するデバッグなので、ここで変更した内容は元のノートブックに反映されません

変更結果を保存したい場合は普通のノートブックと同様、保存してください

セッションの右下停止アイコンをクリックすると、セッションを停止できます

![スクリーンショット 2023-07-16 12.24.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/b412bf25-64ae-0bea-5915-68915a6e7b4a.png)

停止したセッションには参加できなくなり、代わりに右下ゴミ箱アイコンから削除できるようになります

## まとめ

0.9 の Livebook Apps の時点でも凄かったですが、 Multi-Session Livebook Apps によって更に実用的になりました

いろんな処理を Elixir + Livebook で自動化できそうですね