- 怠慢(Laziness)
- 短気(Impatience)
- 傲慢(Hubris)
ってありますが、大して短気でも傲慢でもないので、とりあえずは怠慢になろうとしている新卒1年目の怠慢エンジニアです。
日々のちょっとしたストレス
チームで開発していると開発メンバーの名前を入力しないと行けない場面て多くありませんか?
例えば、
プルリクのレビュワーをセットする
グループチャットツールで送り先を指定する
チームが3、4人程度ならまだ苦でもないのですが10人ぐらいのチームになると、
プルリクやレビューをお願いする度に、
名前を入力して送信相手に漏れがないか確認してという作業を、
一日に何度も何度も何度も、、、
(# ゚Д゚)メンドクセェェ!!
となってしまったので、チームメンバーをワンポチでレビュワーや送信相手に指定できるボタンをブックマークレットで作ってやろうと思い立ちました(^ω^#)
作るもの
To付きをメッセージを送る時、固定メンバーを一括追加できるブックマークレット
※会社ではChatworkを使用しているのでコードはChatwork用です。
必要最小限の機能のみ
javascript: (function(d) { cw_textarea = d.getElementById('_chatText'); cw_textarea.value = cw_textarea.value + '[To:0000]hoge [To:0000]fuga [To:0000]foo '; cw_textarea.focus(); })(document);
これをブックマークのURLに追加すれば完成ですね。
ブックマーククリックすることで
hogeさんとfugaさんとfooさんを追加できましたね。
いい感じです!
これはこれでシンプルでいいのですが、
一つの固定メンバーしか追加できませんし、もう少し高機能にしたいなと、、
ちょっと便利に
さっきのブックマークレットを改良して、以下の機能を追加していきます。
- 開発メンバーかプロジェクトメンバー全員かを選べる
- 名前のありorなしを設定できる
- 名前の最後に改行を追加するかを設定できる
- デザインいい感じに
javascript: (function(d) { var url = location.href, bookmarklet = d.createElement('div'); bookmarklet.id = 'bookmarklet'; bookmarklet.innerHTML += '<div class="buttons">' + '<div class="cw-wrapper">' + '<div class="cw-buttons clearfix">' + '<span class="buttons-name">' + '<img src="https://www.chatwork.com/image/favicon/favicon00.ico">Chatwork:' + '</span>' + '<button type="button" onclick="setChatworkMember(\'dev\')">エンジニアメンバーをセット</button>' + '<button type="button" onclick="setChatworkMember(\'all\')">すべてのメンバーをセット</button>' + '</div>' + '<div class="cw-checkboxes" clearfix>' + '<input id="delete_newline" type="checkbox" name="delete_newline" checked><label for="delete_newline">改行削除</label>' + '<input id="has_name" type="checkbox" name="has_name" checked><label for="has_name">名前あり</label>' + '</div>' + '</div>' + '<div onclick="document.body.removeChild(document.body.firstChild);" class="close">閉じる</div>' + '<style>' + '#bookmarklet {position: fixed; z-index: 10000; top: 10px; right: 10px; width: 340px; height: 105px; border: solid 6px #C5DCEA; border-radius: 5px; box-shadow: black 1px 1px 8px; font-size: 13px; font-family: sans-serif; color: #4f4f4f; text-align: left; padding: 0; margin: 0; background-color: rgb(223, 244, 255); } #bookmarklet .buttons {margin: 10px 0;padding: 10px; height: 68px; } #bookmarklet .buttons button {width: 200px; float: right; } #bookmarklet .cw-buttons, #bookmarklet .stash-buttons {margin: 5px 10px 5px 0px; } #bookmarklet .cw-checkboxes label {margin-right: 10px; } #bookmarklet .buttons-name {font-size: 15px; font-weight: bold; text-align: right; width: 100px; } #bookmarklet .buttons-name img {width:17px; margin: 0px 3px -1px; } #bookmarklet .close {height:20px; width:80px; background-color:#009fff; color:white; text-align:center; font-size:15px; top:-10px; left:240px; line-height:1; padding:4px 0 0 0; position:absolute; cursor:pointer; } #bookmarklet .clearfix:after {content: ""; clear: both; display: block; }' + '</style>'; setChatworkMember = function(group) { if (location.hostname.indexOf('chatwork') == -1) { alert('チャットワークのページで実行してください'); return; } var cw_textarea = d.getElementById('_chatText'); cw_textarea.value = cw_textarea.value + getChatworkToMmmbers(group); cw_textarea.focus(); }; getChatworkToMmmbers = function(group) { var members = [ {'id': 5000000, 'position': 'engineer', 'name': 'Engineer1'}, {'id': 5000001, 'position': 'engineer', 'name': 'Engineer2'}, {'id': 5000002, 'position': 'engineer', 'name': 'Engineer3'}, {'id': 5000003, 'position': 'director', 'name': 'Director1'}, {'id': 5000004, 'position': 'director', 'name': 'Director2'}, {'id': 5000005, 'position': 'director', 'name': 'Director3'} ]; var to_texts = {}; for (var i in members) { var to_text = '[To:' + members[i].id + ']'; if (d.getElementById('has_name').checked) { to_text += members[i].name; } to_text += (!d.getElementById('delete_newline').checked) ? '\n' : ' '; to_texts[members[i].id] = to_text; } var to_members = ''; switch (group) { case 'dev': for (var i in members) { if (members[i].position === 'engineer') { to_members += to_texts[members[i].id]; } } break; case 'all': for (var i in members) { to_members += to_texts[members[i].id]; } break; } return to_members; }; d.body.insertBefore(bookmarklet, document.body.firstChild); })(document);
登録したブックマークをクリックすることで、
ブラウザの右上に上記のようなツールセットが表示されます。
これでボタンをポチるだけで、
Engineer1, Engineer2, Engineer3をセットすることができました!
いい感じですね。
ただ、自分一人だけが使うだけならこれで問題ないのですが、せっかく作ったのでメンバーに配布したいなと、
でもメンバーが増えたり減ったりする度にブックマークレットを配布しなおして再度ブックマークに登録してもらうのは手間だなーと、、
メンテナンス性を高めるために
もっと惰性を追求したいので、ブックマークレットをサーバーから配信する形に変更します。
javascript: (function(d) { var s = d.createElement('script'); s.id = 'bookmarklet'; s.charset = 'UTF-8'; s.src = 'https://[配信サーバーのホスト]:8443/bookmarklet?time=' + (new Date()).getTime(); d.body.appendChild(s) })(document);
ブックマークレットの方はだいぶスッキリしましたね。
続いて、JavaScriptを配信するサーバーサイドです。
サーバサイドで使ったもの
言語:Ruby(2.1.3p242)
サーバー:Webrick
フレームワーク:Sinatra
SSLを使っているサービス上からは外部のJavaScriptを勝手に読み込むことはできなかったので配信サーバーでもSSLを使用しています。
ちなみに、読み込もうとするとSSL通信じゃないとダメだおってエラーが吐かれます。
Mixed Content: The page at 'https://www.chatwork.com/' was loaded over HTTPS, but requested an insecure script 'http://localhost:8443/bookmarklet'. This request has been blocked; the content must be served over HTTPS.
SSLに関しては証明書をオレオレ証明書の作成を参考に作り、myCAディレクトリ以下に設置しました。
Gemfile
source "https://rubygems.org" gem "rack" gem "webrick" gem "openssl" gem "net" gem "sinatra" gem "sinatra-contrib" gem "sinatra-cross_origin", "~> 0.3.1" gem "json"
config.ymlでポートと証明書パスの設定
config.yml
port: 8443 cert_path: certificate: ./myCA/server.crt private_key: ./myCA/server.key
members.ymlでメンバーの情報を管理
members.yml
- {id: 5000000, position: engineer, name: Engineer1} - {id: 5000001, position: engineer, name: Engineer2} - {id: 5000002, position: engineer, name: Engineer3} - {id: 5000003, position: director, name: Director1} - {id: 5000004, position: director, name: Director2} - {id: 5000005, position: director, name: Director3}
以下がメインの配信サーバーです。
bookmarklet_provider.rb
require 'webrick' require 'webrick/https' require 'openssl' require 'sinatra' require 'sinatra/base' require 'sinatra/cross_origin' require 'sinatra/reloader' require 'net/https' require 'json' require 'yaml' set :environment, :production conf = YAML.load_file('config.yml') CURRENT_DIR_PATH = Dir.pwd class BookmarkletProvider < Sinatra::Base register Sinatra::CrossOrigin before do cross_origin content_type :js end get '/bookmarklet' do members = YAML.load_file(File.join(CURRENT_DIR_PATH, 'members.yml')) <<-EOS (function(d) { var url = location.href, bookmarklet = d.createElement('div'); bookmarklet.id = 'bookmarklet'; bookmarklet.innerHTML += '<div class="buttons">' + '<div class="cw-wrapper">' + '<div class="cw-buttons clearfix">' + '<span class="buttons-name">' + '<img src="https://www.chatwork.com/image/favicon/favicon00.ico">Chatwork:' + '</span>' + '<button type="button" onclick="setChatworkMember(\\'dev\\')">エンジニアメンバーをセット</button>' + '<button type="button" onclick="setChatworkMember(\\'all\\')">すべてのメンバーをセット</button>' + '</div>' + '<div class="cw-checkboxes" clearfix>' + '<input id="delete_newline" type="checkbox" name="delete_newline" checked><label for="delete_newline">改行削除</label>' + '<input id="has_name" type="checkbox" name="has_name" checked><label for="has_name">名前あり</label>' + '</div>' + '</div>' + '<div onclick="document.body.removeChild(document.body.firstChild);" class="close">閉じる</div>' + '<style>' + '#bookmarklet {position: fixed; z-index: 10000; top: 10px; right: 10px; width: 340px; height: 105px; border: solid 6px #C5DCEA; border-radius: 5px; box-shadow: black 1px 1px 8px; font-size: 13px; font-family: sans-serif; color: #4f4f4f; text-align: left; padding: 0; margin: 0; background-color: rgb(223, 244, 255); } #bookmarklet .buttons {margin: 10px 0;padding: 10px; height: 68px; } #bookmarklet .buttons button {width: 200px; float: right; } #bookmarklet .cw-buttons, #bookmarklet .stash-buttons {margin: 5px 10px 5px 0px; } #bookmarklet .cw-checkboxes label {margin-right: 10px; } #bookmarklet .buttons-name {font-size: 15px; font-weight: bold; text-align: right; width: 100px; } #bookmarklet .buttons-name img {width:17px; margin: 0px 3px -1px; } #bookmarklet .close {height:20px; width:80px; background-color:#009fff; color:white; text-align:center; font-size:15px; top:-10px; left:240px; line-height:1; padding:4px 0 0 0; position:absolute; cursor:pointer; } #bookmarklet .clearfix:after {content: ""; clear: both; display: block; }' + '</style>'; setChatworkMember = function(group) { if (location.hostname.indexOf('chatwork') == -1) { alert('チャットワークのページで実行してください'); return; } var cw_textarea = d.getElementById('_chatText'); cw_textarea.value = cw_textarea.value + getChatworkToMmmbers(group); cw_textarea.focus(); }; getChatworkToMmmbers = function(group) { var members = #{members.to_json}; var to_texts = {}; for (var i in members) { var to_text = '[To:' + members[i].id + ']'; if (d.getElementById('has_name').checked) { to_text += members[i].name; } to_text += (!d.getElementById('delete_newline').checked) ? '\\n' : ' '; to_texts[members[i].id] = to_text; } var to_members = ''; switch (group) { case 'dev': for (var i in members) { if (members[i].position === 'engineer') { to_members += to_texts[members[i].id]; } } break; case 'all': for (var i in members) { to_members += to_texts[members[i].id]; } break; } return to_members; }; d.body.insertBefore(bookmarklet, document.body.firstChild); })(document); EOS end get '/permission_for_ssl' do 'You can SSL connection with your browser.' end end webrick_options = { :Port => conf['port'], :ServerType => WEBrick::Daemon, :SSLEnable => true, :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, :SSLCertificate => OpenSSL::X509::Certificate.new( File.open(conf['cert_path']['certificate']).read), :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open(conf['cert_path']['private_key']).read), :SSLCertName => [ [ 'CN',WEBrick::Utils::getservername ] ], } Rack::Handler::WEBrick.run BookmarkletProvider, webrick_options
クロスドメイン対策のためにCrossOriginをbeforeで設定しています。
/bookmarkletには、先ほどのブックマークレットを直書きしてmembersをyamlから読み込むようにした感じですね。
またオレオレ証明書なので、ブラウザから最初にこのサーバーにアクセスするとき警告が出てしまいます。
具体的には、以下みたいな感じです。
[Chromeの場合]
GET https://localhost:8443/bookmarklet?time=1419733313579 net::ERR_INSECURE_RESPONSE
[Safariの場合]
Failed to load resource: このサーバの証明書は無効です。“localhost”に偽装したサーバに接続している可能性があり、機密情報が漏えいするおそれがあります。
そのため、初回のみブラウザからアクセス許可を出す必要があり、そのときように/permission_for_sslを追加しました。
webrick_optionsではポート番号とSSL、それとデーモン起動の設定をしています。
これで完成です!
ruby bookmarklet_provider.rb
を実行すればサーバーが起動できるので、あとはブックマークレットをポチるだけですね!!
まとめ
これで怠慢エンジニアにまた一歩近づくことができました!
ちょっとこだわりすぎてサーバーまで設置してしまいましたが、、
会社のAWSなら月1万円ぐらいまで自由に使えるので、そこに設置してチームで運用してみたいと思います。
コードはGithubに置いておくので興味のある方はご自由にお使いください。
また、拙いコードですので改良点などのご指摘は大歓迎です。
ブックマークレット作成時に参考にさせていただいサイトソーシャルてんこ盛り - actyway -
この記事はQiitaに投稿したものの転載です。