LoginSignup
4

More than 3 years have passed since last update.

Slack Workflowの申請内容を一覧(CSV)にする(with Elixir)

Last updated at Posted at 2020-01-11

はじめに

スクリーンショット 2020-01-12 1.36.59.png

% uname -a
Darwin 20-02noMacBook-Pro.local 19.2.0 Darwin Kernel Version 19.2.0: Sat Nov  9 03:47:04 PST 2019; root:xnu-6153.61.1~20/RELEASE_X86_64 x86_64
% elixir -v
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.9.4 (compiled with Erlang/OTP 22)

なぜ一覧化するの?

  • そもそもなぜ一覧化が必要なのでしょうか
  • 私が所属している会社では、有給休暇の申請はSlackのワークフローで行うことになっています
    • 他の各種申請(経費精算、始業時間変更等)もとにかく申請が必要だったものはあるときから、すべてSlackに移行しています
  • 各人の有給休暇の取得日数を集計したりする管理部門の人は一覧で欲しかったりするのではないでしょうか
    • 他の良い方法はたくさんあるかもしれません
  • あれ? あの人、今日いないなあ、休みだっけ? とワークフローのチャネルをスクロールするのが面倒だったりしませんか?
    • カレンダー共有するとかモダンなやりかたがあるかもしれません
  • 詰まるところ、私が古臭い人間だから一覧が欲しいのです:man_tone1:
  • 私は「承認」ボタンを押したあと、スプレッドシートに手動でコピペしていました
    • 最初のうちはそれでもよかったのですが、2ヶ月くらいそうやって続けてきてだんだん面倒になってきました
  • 一覧は、Phoenixで! とおもわないわけではなかったのですが、まだPhoenixは修行中なので、まずは肝の部分をCSVに書き込むことをやってみました

ソースコード

  • TORIFUKUKaiou/slack_workflow
  • ワークフローは多種多様だとおもうので、私が所属しているワークスペースのチャネルの申請内容はギリギリ、パースできている気がしますが、みなさまのところのチャネルで同じようにうまく動くかどうかはわかりません
    • 私が所属しているワークスペースでも、まあそれなりにというところくらいなので、みなさまのところではそのままでは動かない可能性は大です
  • 以下、一覧化したいあなたに向けて、ポイントになりそうなところを説明します
  • なにはともあれElixirをインストールしましょう!
    • Installing Elixir
    • Windowsの方は上記ページからインストーラをダウンロードできます
    • macOSの方はasdf-vmでインストールするのがオススメですが、手軽に始めるならHomebrewでインストールするでもいいかもしれません
      • 上のページはHomebrewでのインストール方法が最初に書いてありますし

SlackWorkflow.Applications.run/3

lib/slack_workflow/slack_applications.ex
defmodule SlackWorkflow.Applications do
  def run(channel, oldest, latest) do
    members = fetch_members()

    channel_id = fetch_channels() |> Map.fetch!(channel)

    SlackWorkflow.SlackConversationsHistory.fetch(channel_id, oldest, latest)
    |> Enum.filter(&Map.get(&1, "blocks"))
    |> Enum.map(&{Map.get(&1, "blocks"), Map.get(&1, "ts"), Map.get(&1, "username")})
    |> Enum.map(fn {blocks, ts, name} -> {Enum.at(blocks, 0), ts, name} end)
    |> Enum.filter(fn {blocks, _, _} -> Map.get(blocks, "text") end)
    |> Enum.map(fn {blocks, ts, name} -> {Map.get(blocks, "text"), ts, name} end)
    |> Enum.filter(fn {text_map, _, _} -> Map.get(text_map, "type") == "mrkdwn" end)
    |> Enum.map(fn {text_map, ts, name} -> {Map.get(text_map, "text"), ts, name} end)
    |> Enum.map(fn {text, ts, name} -> {parse(text, members), ts, name} end)
    |> Enum.map(fn {keywords, ts, name} ->
      {Keyword.put(keywords, :created_at, date(ts)), ts, name}
    end)
    |> Enum.map(fn {keywords, ts, name} ->
      {Keyword.merge(keywords, [{"url" |> String.to_atom(), url(ts, channel_id)}]), name}
    end)
    |> Enum.reduce(%{}, fn {keywords, name}, acc_map ->
      list = acc_map |> Map.get(name, [])
      acc_map |> Map.merge(%{name => [keywords] ++ list})
    end)
  end
  • Applicationsは申請たちの意味です
  • conversations.history APIを呼び出した結果、blocksというキーをもつメッセージだけが、私の所属しているワークスペースのチャネルでは申請内容を表しているようにみえました
    • 最初にこれで絞りこんでいるので、もうこの時点でうちは違うよの場合は、書き換えが必要です
    • iex -S mix して SlackWorkflow.SlackConversationsHistory.fetch(channel_id, oldest, latest) を呼び出してください
    • きれいな形でJSONがみえるのであなたが所属しているワークスペースでの申請内容の傾向をつかんでください
  • SlackへのリンクURLを作りたくて、tsキーの値をとってなんとなくURLをつくっていますが正しいのかどうかわかっていません

SlackWorkflow.Applications.parse/2

lib/slack_workflow/slack_conversations_history.ex
  defp parse(s, members) do
    first = s |> String.split("\n") |> Enum.find_index(&(&1 =~ ~r/\*.+\*/))

    second =
      s
      |> String.split("\n")
      |> Enum.slice((first + 1)..-1)
      |> Enum.find_index(&(&1 =~ ~r/\*.+\*/))
      |> Kernel.+(first)
      |> Kernel.+(1)

    index = if second - first > 1, do: first, else: second

    s
    |> String.split("\n")
    |> Enum.slice(index..-1)
    |> Enum.reject(&(&1 == ">"))
    |> Enum.reject(&(&1 == ""))
    |> Enum.chunk_every(2)
    |> Enum.filter(&(length(&1) == 2))
    |> Enum.reduce(Keyword.new(), fn [k, v], keywords ->
      keywords
      |> Keyword.merge([{k |> key(), v |> value(members)}])
    end)
  end
  • 一番の難関はここでしょう
  • 私の所属しているワークスペースのチャネルでは申請内容では、いろいろ試行錯誤して、いまのところ実行時エラーは起きないくらいの実装内容となっております
    • 出力結果に、Slackへのリンクが含まれているから、一覧としては、まあいっかなあくらいの感じです
  • *申請内容* のように*で囲まれている文字列が含まれているところがキーでその次にくるやつが値だろうという実装をしています
    • ここもみなさまのワークフローメッセージとは違いがある可能性が高いところだとおもいます
  • 申請内容文字列を改行でsplitしたListのここから先が申請内容キーの始まりですよというものをindexにいれているつもりです
    • firstとかsecondとか書いているのは私の所属しているワークスペースでは次の2種類があったためです
    • これがみなさまに伝わるのは自分でも疑わしくおもっています
    • とにかく一筋縄ではいかないことをお伝えしておきます
:information_source: *申請内容*
>  :neutral_face: *申請者* ← ここからキーとして解析したい
&gt; <@UC1234567>\n&gt; ← 申請者の値
&gt; :date: *日付* ← 日付キー
&gt; 2020/01/07 ← 日付の値
...
&gt; *申請者*  ← ここからキーとして解析したい
&gt; <@UC1234567>\n&gt; ← 申請者の値
&gt; *経費精算フォーム* ← 経費精算フォーム キー
&gt; <https://drive.google.com/open?id=hogehogehoge> ← 経費精算フォームの値

動かし方

  • なにはともあれ動かしてみたい方向けに説明をします
  • まずはElixirインストールしてください

ソースコード取得と依存関係の解決

% git clone https://github.com/TORIFUKUKaiou/slack_workflow.git
% cd slack_workflow
% mix deps.get

Slack APIのtokenを取得して環境変数SLACK_TOKENに設定

  • channels:historychannels:readusers:readのScopesをもったトークンを取得してください
  • Slackアプリの作成方法は下記の記事がとても参考になるとおもいます!
  • このトークンを環境変数SLACK_TOKENに設定してください

Team domainを環境変数SLACK_TEAM_DOMAINに設定

  • たとえば、あなたが所属しているワークスペースがelixir.slack.comだったら、elixirSLACK_TEAM_DOMAINに設定してください

iex 編

% iex -S mix
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex> channel = "workflow"
"workflow"
iex> oldest = 0
0
iex> latest = Timex.now |> Timex.local |> Timex.end_of_day  |> DateTime.to_unix
1578754799
iex> SlackWorkflow.create_csvs(channel, oldest, latest)
:ok
  • channelはワークフローがながれているチャネル名
  • oldestlatestはunix timeで指定します
  • oldestが0のときは、一番最初のメッセージから取得します
  • oldestlatestの期間を指定して習得することができます

escript編

  • 単純に、mix escript.buildしてできたバイナリでは動きません
  • timexが依存しているtzdata.etsファイルというものの場所を設定してあげる必要があります
% iex
iex> Tzdata.Util.data_dir
"/Users/torifukukaiou/slack_workflow/_build/dev/lib/tzdata/priv"
  • なんてのがでますから、これをconfig/config.exsに設定してください
config/config.exs
config :tzdata,
  data_dir: "/Users/torifukukaiou/slack_workflow/_build/dev/lib/tzdata/priv",
  autoupdate: :disabled
  • この設定をしたあとに
% mix escript.build
  • してください
  • slack_workflow というバイナリができてるはずです
% ./slack_workflow --channel workflow --oldest 0 --latest 1578754799
  • てな感じでコマンドで動かせるはずです

さいごに

  • この構想を同僚に話したところ、パースが面倒だから、どうせやるならSlackアプリとしてつくる(※ここの部分が私の知識不足であまりぴんときていません)とか他のことを考えたほうがいいですよと言われました
  • おもいついてしまったものはなかなか止められません
  • こういうのキライじゃないし、やるだけやってみようとおもいました
  • たしかに面倒くさい部分はありましたが、Elixirを使ってパースの処理を書いているとだんだんと楽しくなってきました
    • 作っているときはiexでちょっとずつ試しながら試行錯誤を繰り返しました
  • そう! Elixirは楽しいのです!
  • Enjoy! with Elixir.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4