ひさびさのQiita投稿ですが、福岡および私の周りのElixir環境は、Qiitaを書き始めた5ヶ月前と比べ、激変しており、自社・他者両方のプロダクト開発や、業務改善ツールにElixirを選択される機会が、相当増えました
私自身も、公私共に、Elixirを書かない日が無くなったレベルで、大きく動きはじめています
さて、そんな中で、「Elixirを使って、ジョブ管理ツールを作る」という、なかなか興味深いプロダクト開発が始まりそうです
ということで、ジョブ管理ツールのデータ構造やUIの原型のようなものを、少しコード化してみたいと思います
ビジュアライズされたジョブ管理ツール
ジョブ管理ツール、といえば、Hinemosのような、ジョブを一覧して、ジョブの実行状況を表示したり、リランをかけたりと、割と地味な操作画面というのが定番だと思います
一方で、商用ツールの中には、ビジュアルなジョブツリーやジョブネットワークを表示し、ジョブ同士の先行関係やブロック状況を一目で分かるような操作画面を備えるものもあります(私の前職がそういうものを使ったり、販売していました)
そういうビジュアライズされたジョブ管理ツールを自作してみようと思い立ちました
先行関係のあるジョブを表現するデータ構造
まずジョブを、どのようなデータ構造で表現するか、から始めます
実際にジョブを管理するのに充分な情報を一気に作り込むと大変なので、まずは「ジョブ名」だけのリストを考え、追々、その中身を充実させていく方針でいきます
シンプルに、先行関係のあるジョブのリストは、こんな感じにします
[
"買い物に行く",
"カップ麺を買う",
"帰る",
"カップ麺のフタを空ける",
"カップ麺にお湯を注ぐ",
"箸を持ってくる",
"カップ麺を食べる"
]
カップ麺は、シーケンシャルに食すジョブです
ここに、健康も考え、サラダも一緒に食べるとしましょう
[
"買い物に行く",
[ "カップ麺を買う", "サラダを買う" ],
"帰る",
"カップ麺のフタを空ける",
"カップ麺にお湯を注ぐ",
[ "箸を持ってくる", "サラダのフタを空ける", "サラダを食べる" ],
"カップ麺を食べる"
]
サラダを食べ切らないと、カップ麺が食べれない、なんとも言えない構造だが、まぁ、これで良しとしよう
さて、この階層リストのままだと、ネットワーク構造を作ることはできないけど、途中に相乗りする構造は、後々作るとして、まずはこのデータをビジュアライズしてみます
Phoenix 1.3でWebアプリの雛形を構築する
Node.js等のインストールは済ませていて、Phoenix 1.3が使える状態から開始します
# mix phx.new jobber --no-ecto
Phoenix 1.3を使える状態までの構築は、SlideShareに作り方をアップしているので、ご参照ください
ジョブリストをWebページにリスト表示する
ジョブリストを、HTMLのリスト(<ul>~<li>~</ul>)に変換します(いったん、10階層以上は、エラーで落とすようにします)
def html_list( items \\ "", start \\ "<ul>\n", stop \\ "</ul>\n", pre \\ "<li>", post \\ "\n" ) do
items |> probe( start, stop, pre, post )
end
def probe( items, start, stop, pre, post, count \\ 0 ) when count < 10 do
case is_list( items ) do
true -> start <> List.to_string( Enum.map( items, &( probe( &1, start, stop, pre, post, count + 1 ) ) ) ) <> stop
false -> pre <> items <> post
end
end
HTMLから、上記を呼んでみます
<%= raw( Listing.html_list(
[
"買い物に行く",
[ "カップ麺を買う", "サラダを買う" ],
"帰る",
"カップ麺のフタを空ける",
"カップ麺にお湯を注ぐ",
[ "箸を持ってくる", "サラダのフタを空ける", "サラダを食べる" ],
"カップ麺を食べる"
]
) ) %>
Phoenixを起動します
# iex -S mix phx.server
ブラウザで「http://localhost:4000 」にアクセスすると、以下のような画面が表示されます
うむ、なんとなく良い感じ
ジョブリストをWebページにテーブル表示する
リスト表示と同じ構造で、HTMLテーブル表示もカンタンに作れます
…
def html_table( items \\ "", start \\ "<tr>\n", stop \\ "</tr>\n", pre \\ "<td>", post \\ "</td>\n" ) do
"<table border='1'>" <> ( items |> probe( start, stop, pre, post ) ) <> "</table>"
end
…
呼び出し側も修正します
<%= raw( Listing.html_table(
[
"買い物に行く",
[ "カップ麺を買う", "サラダを買う" ],
"帰る",
"カップ麺のフタを空ける",
"カップ麺にお湯を注ぐ",
[ "箸を持ってくる", "サラダのフタを空ける", "サラダを食べる" ],
"カップ麺を食べる"
]
) ) %>
地味に、Elixir 1.5から、カッコを書かなくてもエラーが出なくなった、recompileを実施します
iex> recompile
ブラウザで確認すると、以下のように、テーブル化されて表示されます
ほんのちょっとだけ、それっぽい表示になったかな