読者です 読者をやめる 読者になる 読者になる

SiG Staff Blog

やっていることをつらつらと

slack + hubot + Backlog Apiでプロジェクトの予定と実績の修正をとる

色々あって便利ですよね、Backlog

Backlogいいね!

これまで、クラウドサービスの使用禁止という世間の流れから逆流した開発をさせられてて、

Redmineでタスク管理をしていたのですが、ようやくBacklogの使用許可がおりて使っております。

ticketも使えるし、wikiもあるし、Gitもあるしいいよね。社外でも見れるし。

Backlogよくないね!

Redmineと違って、サブプロジェクト作れない、チケットが親子までしか作れない、Gitのレビューコメントがちょいと見にくいとかあるんですね。

APIが微妙に使いにくいとか、webhookがちょっと微妙とか・・・。

でも、これらは無くても全然プロジェクト進行できるしいいんですよ。

困るのはサマリー系が全然ない事。予定・実績とかの管理が全然できないんです。

どうしたもんか・・・。

なければ作ればいいって偉い人が言ってるよ

Backlog API課題の情報を取得するのがあるやん。

プロジェクトの課題を全部取ってきて、自分でサマリーしてSlackに通知すればいいんじゃ?

ってことで、herokuにslack連携を済ませてあるhubot がいるので彼の仕事を増やしてあげる事にしました。

欲しいのはソースですよね・・・。

動かし方

必要な物は

  • Backlogのトーク
  • スペースID
  • プロジェクトの情報

です。これらを

  • api_key
  • space_id
  • project_list

にいい感じにセットしてあげてください。

で、botにメンションを付けて

@botName give me 【呼出用のプロジェクト名】

ってすれば動いてくれます。リクエスト投げまくるので結果が返ってくるまで少し時間かかります。

ってことで、ソース

ソース

少し長いです。

  robot.respond /give me[ ]?(.*)/i, (msg) ->

    #Backlogのトークンを設定する。個人設定から取得する
    api_key = "【トークンの設定】"

    #BacklogのスペースIDを設定する。https://[ここの部分].backlog.jp
    space_id = "【スペースIDの設定】"

    #プロジェクトのリストを取得する
    project_list = 
      "【呼出用のプロジェクト名】":
        name: "【画面表示用のプロジェクト名】"
        id: "【backlogのprojectのID】"
        start: 1
        end: 300

    target_project = msg.match[1]
    
    if not (target_project? and target_project of project_list)
      project_names = ""
      for key of project_list
        project_names += key + ", "
      msg.send "どのプロジェクトなの? -> #{project_names}"
      return -1
    
    target_data = project_list[target_project]
    target_name = target_data["name"]
    target_id = target_data["id"]
    target_start = target_data["start"]
    target_end = target_data["end"]

    if not (target_name? and target_id? and target_start? and target_end? and isFinite(target_start) and isFinite(target_end) and target_start < target_end)
      msg.send "プロジェクトの設定値を確認してください。 -> name: #{target_name}, id: #{target_id}, start: #{target_start}, end: #{target_end}"
      return -1

    #小数点以下を切り上げするためのfunction
    floatFormat = ( number, n ) ->
      _pow = Math.pow( 10 , n )
      Math.round( number * _pow ) / _pow

    #Backlog apiを使って課題の集計をするfunction
    # @param bl_project_id プロジェクトID (プロジェクト作成した時に指定する値。課題の前に付くあれ)
    # @param start 対象にするチケットの開始位置
    # @param end 対象にするチケットの終了位置
    call_backlog = (bl_project_id, start, end) ->
      #ループのサイズ(ループをStopさせる用)
      loop_sise = end
      #ループの開始位置
      loop_start = start
      #ループの終了位置
      loop_end = loop_start + loop_sise - 1
      #課題データ
      actual = {};
      #課題データを貯めるリスト
      actual_list = [];
      #作業位置
      finish_cnt = 0

      #プロジェクトIDを取得する
      back_log_url_project = "https://#{space_id}.backlog.jp/api/v2/projects/#{bl_project_id}?apiKey=#{api_key}"

      request = robot.http(back_log_url_project)
                     .get()
      request (err0, res0, body0) ->
        json0 = JSON.parse body0
        project_id = json0["id"]
        
        #課題の数を取得する。
        #課題の取得が非同期なのでうまく止まるようにチケットの最大値を取得する。
        #Promiseを使えればきっとうまくできるはずなんですが。
        back_log_url_count = "https://#{space_id}.backlog.jp/api/v2/issues/count?apiKey=#{api_key}&projectId[]=#{project_id}"

        request = robot.http(back_log_url_count)
                      .get()
        request (err1, res1, body1) ->
          json1 = JSON.parse body1
          
          #設定されている値がチケットの数よりも多いと、複数回サマリー結果が出てしまうので
          #チケットの数までループするように変更する
          ticket_max_size = json1["count"]
          if loop_end > ticket_max_size
            loop_end = ticket_max_size
            loop_sise = loop_end - loop_start + 1

          for i in [loop_start..loop_end]
            
            #課題を取得
            back_log_url = "https://#{space_id}.backlog.jp/api/v2/issues/#{bl_project_id}-#{i}?apiKey=#{api_key}"

            request = robot.http(back_log_url)
                          .get()
            request (err, res, body) ->

              #上手くパースされないので特殊文字を消す
              body_new = body.replace(/\\n/g, "")
                          .replace(/\\'/g, "'")
                          .replace(/\\"/g, '')
                          .replace(/\\&/g, "")
                          .replace(/\\r/g, "")
                          .replace(/\\t/g, "")
                          .replace(/\\b/g, "")
                          .replace(/\\f/g, "")
              json = JSON.parse body_new
              
              
              #エラーチェック
              if ("errors" of json)
                finish_cnt = 9999
                return 0
              else
                #値が入ってこない時があるので、デフォルト設定
                eh = if json["estimatedHours"]? then json["estimatedHours"] else 0
                ah = if json["actualHours"]? then json["actualHours"] else 0
                assignee = json["assignee"]

                name = if ( assignee? && "name" of assignee) then json["assignee"]["name"] else ".未設定 "

                actual = 
                  name: name
                  estimatedHours: eh
                  actualHours: ah
                  status_unopend: 0
                  status_opened: 0
                  status_treatmentted: 0
                  status_closed: 0

                #チケットの状態を保持する
                if json["status"]["id"] == 1
                  actual["status_unopend"] = 1
                else if json["status"]["id"] == 2
                  actual["status_opened"] = 1
                else if json["status"]["id"] == 3
                  actual["status_treatmentted"] = 1
                else if json["status"]["id"] == 4
                  actual["status_closed"] = 1

                #後でサマリーをしないと値が消えてしまうので
                actual_list.push(actual);

              #処理件数をincrement
              finish_cnt += 1

              #処置が全部終わったら出力する
              #非同期処理の中で1つかこの処理が走らない
              if finish_cnt >= loop_sise

                #ユーザー単位でサマリーする
                output = {}
                for actual, idx in actual_list
                  key_name = actual["name"]
                  if key_name of output
                    data = output[key_name]
                    data["estimatedHours"] += actual["estimatedHours"]
                    data["actualHours"] += actual["actualHours"]
                    data["status_unopend"] += actual["status_unopend"]
                    data["status_opened"] += actual["status_opened"]
                    data["status_treatmentted"] += actual["status_treatmentted"]
                    data["status_closed"] += actual["status_closed"]
                    output[key_name] = data

                  else
                    output[key_name] = 
                      estimatedHours: actual["estimatedHours"]
                      actualHours: actual["actualHours"]
                      status_unopend: actual["status_unopend"]
                      status_opened: actual["status_opened"]
                      status_treatmentted: actual["status_treatmentted"]
                      status_closed: actual["status_closed"]
                
                str = " *-------- #{target_name} プロジェクト実績 --------* \n"

                #出力をする
                for k,v of output
                  str += "   * #{k} *: #{v["actualHours"]}/#{v["estimatedHours"]} H( #{floatFormat(v["actualHours"] / 8, 2) } / #{floatFormat(v["estimatedHours"] / 8, 2)} 日) [ 未:#{v["status_unopend"]}, 中:#{v["status_opened"]}, 済:#{v["status_treatmentted"]}, 完:#{v["status_closed"]} ] \n"
                
                msg.send str

                return 0

    #指定したプロジェクトのサマリーを出力
    call_backlog(target_id, target_start, target_end)

    #ヘッダとしてだす
    msg.send "ちょっと待ってね♪"

出力内容

*-------- XXXX プロジェクト実績 --------*
>   ユーザーA: 0/30 H( 0 / 5 日) [ 未:10, 中:0, 済:1, 完:0 ] 
>   未設定 : 0/1074 H( 0 / 134.25 日) [ 未:109, 中:0, 済:0, 完:0 ] 
>   ユーザーB: 0/0 H( 0 / 0 日) [ 未:0, 中:1, 済:0, 完:0 ]

もうちょっと上手くできるんでは?

Promiseとか使えばコールバックのネストが無くなるんじゃ?

coffeeが1.6だったのでPromiseが使えなかったんです。 どうすれば使えるようになるんですかね・・・。

未設定のユーザーって一番上か下に出たほうが良いんじゃ

作った後に気が付いたんですが、ソートするのがめんどくさくて。。。 最後の出力の所でごにょごにょして対応しようと思ってます。

作ってみて

そいういえば、非同期で送信するんだった。ってので思ったより嵌りました。(Promise使えなかったし)

ちょっと遅いし、Backlogにちょっとした負荷がかかってしまうのが気になるんですが、統計が取れるようになったのは便利ですね~

PodCastで情報集め

音楽だけじゃなかったんですね

バイリンガルニュース

知人に英語の勉強にバイリンガルニュースってPodCastが面白いよって言われて、
ゴシップ的なニュースを英語と日本語でやってるんかなぁって思いつつ、
とりあえず聞いてみたんですが、、、

サイエンティフィックだし、アカデミックだし、下ネタだし、なんか普段触れない興味深い話が多くて嵌ってしまった。

バイリンガルニュース (Bilingual News)

バイリンガルニュース (Bilingual News)

  • Michael & Mami
  • 言語コース
  • ¥0

数年前からしているみたいですねー。 スポンサーがいないらしいので、アプリを月額(300円以下だった)で買って続けてもらえるように支援してます。

正直、英語の部分は分からない事が多いけど、雰囲気で分かるようになるのかな??

mosaic.fm

NginxとかLet’s EncryptとかES7とか面白い話が沢山。 後ろで小さく音楽が流れてるのが気になる。 そんな番組。

mozaic.fm

CodeLunch.fm

こっちも同じような感じ。ちょっとだけサーバーサイドよりの話が多いかもかも。

codelunch.fm

Rebuild.fm

有名なやつ! この前、バイリンガルニュースとクロスしてた。

rebuild.fm

ただですよ

内容が内容だけに、聞くことに必死になって、作業しながらとかできないの。 みんな聞き流すのかな。不思議。

phpのオレオレフレームワークを作ろう - Node.jsを試す(準備)

Node.jsを使ってファイルを今風にしよう

悲しいかな・・・

NodeってことはNodeBrewか?nvmか? MacLinuxか? って思われますよね。ですよね。

でも使うのはWindowsです。 田舎のしがないプログラム屋はwindowsなのです。Officeがちゃんと動くんです。えぇ。

なので、Nodistですね!

とにかく、Node.jsいれましょう

Node.jsってなんの役に立つん?

サーバーサイドばかりやってると

  • jsってAjaxでアクセスしてくるあいつでしょ?
  • DOMの内容を書き換えたりするやつでしょ?
  • ブラウザで動くのにサーバーサイドって?

という感じですの。

感じとしては、

APサーバーを立てなくても動くPHPみたいなもん。

です。

Q: でも、PHPのF/W作るんじゃないんか?何で使うん?  
A: なんか知らんけど、
・CSSのメタ言語(SCSS -> CSS)とか
・jsのトランスパイラ(最新言語仕様でjs書いて、今ブラウザに実装されているjavascriptに書き直してくれるやつ)とか
・画像の圧縮とか
。パッケージ化とか

ができるようになって、画面周りを作るのが楽になるらしい。

NodeやなくてNodist?

Node.jsはnpmってのでライブラリを管理しているんだと。 で、バージョン依存とかがあるので、Nodeのバージョン入れ替えたりする必要がある事が多いそうだ。 なので、Nodistを入れて切り替えれるようにしているんだって。

って事でインストール

github.com

Githubの下の方に書いてるREADMEの所に、with the installer ってあるので、 そのリンク先からファイルを落としてきて、YESマンになってインストール

終わったら

コマンドライン

nodist dist

って実行するとズラーって何かが出てくるけど、それはバージョンだ。

nodist use 7.8.0

って感じでコマンドを叩くと、なんかダウンロードみたいなのがされる。 終わったら、nodistって叩くと7.8.0と表示される。インストールできたぽい。

node -v

ってやるとv7.8.0って出る。

完了だ。簡単や。

同時に、パッケージ管理のnpmも入っているんやが己が古い時があるので、更新コマンドを叩く。

npm update -g npm

うむ、これでNode使う準備はできたの。

エディタを入れて置く

AtomとかSublimeとかあるが、phpdocとかがポップアップで表示されるという機能が嬉しかったので、VS Codeを入れて使っていくよ。 エレクトロンで作られているらしくて、Linuxでも使える。 Extentionで設定とかもクラウドで保存できるし、イイね。 code.visualstudio.com

という事で、、、

次はサーバーサイドの環境を作っていこいう。 そう、Dockerは使わないで、XAMMPだ。。。 サーバーは簡単に作りたいもんね。うんうん。

ウッドパームレストを自作してみる - その2

ということで、仕上げ作業

まずは色をいい感じにするゾ

前回、やすりをかけて整形が終わったので、今回はオイルで色塗り。

濃いめの色が好きなのでオーク色のオイルステインを布で塗りぬり。

4回ぐらい塗りぬり。ダークオークみたいにならないかな。。。

ほんで、1時間ぐらい乾かして。。。

滑り止め装着!

木工用ボンド!ゴムは角を丸くしてなんかそれっぽく。

f:id:Bee_Flim:20170330133819j:plain

装着してみる!!

結構な高さがあるキーボード

f:id:Bee_Flim:20170330133804j:plain

寄せてみるととこんな感じ。

f:id:Bee_Flim:20170330133755j:plain

いい高さですね。ほぼぴったり。

f:id:Bee_Flim:20170330133744j:plain

使用感

とても良好です
角度が有りと無しの2種類を作ってみたんですが、FILCO純正の様に手前に角度が結構ついてるほうが使いやすくて、
角度無しだとちょっと手の位置が高い気がする。5ミリぐらいの差だけど結構でかいんだなぁ。
次作る時は気にしてみよう。

板:数百円ぐらい サンドペーパー:200円ぐらい ゴム:120円ぐらい オイルステイン:500円ぐらい(結構残ります)

千円行かないぐらいでできたよ!

ウッドパームレストを自作してみる - その1

なんか腕がだるい

仕事に使う道具は良い物が良いと思って、初めて高級キーボード(FILCO)購入して、
これはいいなぁとカチャカチャ鳴らしながらタイピングしていたんだけれども、
今まで使っていたキーボードと違いちょっと段差が大きいのである、このキーボード。

電気屋で良く見る腕を置くやつ。今までは高さがあんまりないキーボードばかり使っていたので需要が解らなかったけど、こういう時にいるのか!
って調べてみると、FILCO専用のがあるじゃないですか!しかもウッディでいい感じ。

・・・

ただの木の板のくせに高い。。。

いっその事、作ってみよう

昔に棚を作ったあまりの木の板があるので、そいつを使って作っちゃおう。
そうすれば、サンドペーパー代ぐらいやん。

って事で、調査や!

FILCO用のを写真で見てると半分ぐらいから少し厚みが薄くなってるのね。タイプする時の角度用にちょっとつけてるのかな?
木のサイズは440×81×20㎜(底部クッション含む) なので、偶然丁度いい感じの厚みと長さ。

仮にそのままで使ってみると、重量が無いせいか滑って動いてしまうので、ゴムとかを付けて滑り止めを付けないといけないですね。

設計はできたので後はやるのみ!

結構削りそうなので、40~80ぐらいの数枚と、細かく削るために120、化粧磨きで240のサンド。滑り止めのゴム1枚をホームセンターで購入。

f:id:Bee_Flim:20170329212608j:plain

後は、粗いサンドでひたすら角をとる。

f:id:Bee_Flim:20170329212508j:plain

ひたすら削る。

・・・。

ひたすら傾斜をつける。

・・・。

出来た!!

f:id:Bee_Flim:20170329212558j:plain

あとは、オイルを塗って滑り止めをつければ、完成だ!!

けど、もう夜なのでまた来週にもちこしし

phpのオレオレフレームワークを作ろう - イントロ

プロジェクトに入るといつも・・・

大体、どこかの賢い人がFuelとかLaravelとかSymphonyとかを使って色々いい感じに開発できるように環境を作ってくれている。
けどね、良かれと思った事が過剰サービスになってるところが良くあるんですよよ。

いやだなぁって思うこと

  • どのDBでも動くから~とか言われても、DBがmysqlからPostgresに変わった事なんて一度もないざんす。
  • 人が増えるたびにORMをメンバーに学習してもらうのが面倒くさい。
  • ちょっとでも条件文を作ろうと思うと、不具合を埋め込まれやすい。 (コードレビューをそこまでしてられない時もあるんです)
  • オラオラとphp周りのライブラリは取り込んでいるが、フロントエンドの技術が全く入っていない。

じゃ、作ればいいやん

ってことで、下の要件を満たすものを作っていくよ。

  • レイアウトは定義したのを使える。
  • 当然画面ごとにレイアウトは変えれる。
  • テンプレートエンジンを使ってリストとかはいい感じに表示できる。
  • DBはプレーンに近いSQLで叩ける。
    • ただし・・・
      • 論理削除などは自力。
      • オブジェクティブな構造では戻ってこない。
  • ログイン機能や検索機能、登録機能とかは簡易にできるように、ラッパークラスがある。
  • フロント回りの技術(メタ言語、webpackなどなど)が使えるようになっている。
  • レスポンシブにできるようになってる。
  • 説明書がちゃんとある(重要)

うん、いい感じ。

ベースに何使おう

FuelとかSymphonyは使ったことあるし、Laravelは少し遅いらしいし。。。
あ、Zend Framework2使ってみよう!!有名だし、きっといい感じのはずや!(この決断が後で悲しい思いをする・・・)
テンプレートは使ってみて不満が無かったのでmustacheで。
画面周りはReactを入れてみたいけども、それだと開発者が少なそうだからmaterialize cssを使おう。
スクランナーはとりあえずGulpで。 説明書はコードベースで書けるsphinxを使ってみよう!

これで一旦実装していってみよう!!! ある程度できたらGitHubに公開していくよ。