4.0.2 を先ほどリリースしました。Firefoxのテーマによっては、ポップアップの背景色が白以外となってしまう問題の対処となります。
自分の手元では再現できなかったのですが、報告していただいた方に試してもらいながら解決しました。
4.0.2 を先ほどリリースしました。Firefoxのテーマによっては、ポップアップの背景色が白以外となってしまう問題の対処となります。
自分の手元では再現できなかったのですが、報告していただいた方に試してもらいながら解決しました。
先週の第15回redmine.tokyoで紹介したスクリプトです。
REST APIを使って複数の子チケットをまとめて作成します。
作業を子チケットに分割して運用しているようなところだと、かなりうれしいのではと思います。 例えば、親として機能開発があって、その下に設計、製造、テストみたいな子チケットを作るような場合、これで1クリックで定型的な子チケットが作れるようになります。
.*
Bottom of issue detail
$(function() { var projectId = $('#issue_project_id').val(); var trackerId = $('#issue_tracker_id').val(); var subject = $('#issue_subject').val(); var priorityId = $('#issue_priority_id').val(); var parentIssueId = ViewCustomize.context.issue.id; // 子チケットとして作成する情報 var issueChildren = [ { 'issue': { 'project_id': projectId, 'tracker_id': trackerId, 'subject': subject + ' - 子チケット1', 'priority_id': priorityId, 'parent_issue_id': parentIssueId } }, { 'issue': { 'project_id': projectId, 'tracker_id': trackerId, 'subject': subject + ' - 子チケット2', 'priority_id': priorityId, 'parent_issue_id': parentIssueId } }, { 'issue': { 'project_id': projectId, 'tracker_id': trackerId, 'subject': subject + ' - 子チケット3', 'priority_id': priorityId, 'parent_issue_id': parentIssueId } } ]; var link = $('<a title="子チケットの一括作成" class="icon icon-add" href="#">子チケットの一括作成</a>'); $('#issue_tree').before($('<p>').append(link)); link.on('click', function() { if (!confirm('子チケットをまとめて作成します。よろしいですか。')) { return; } // チケット作成処理(非同期)を順次実行し、最後にリロード var defer = $.Deferred(); var promise = defer.promise(); for (var i = 0; i < issueChildren.length; i++) { promise = promise.then(createIssue(issueChildren[i])); } promise .done(function() { // 成功したらリロード location.reload(); }) .fail(function() { alert('失敗しました'); }); defer.resolve(); }); function createIssue(issue) { return function() { return $.ajax({ type: 'POST', url: '/issues.json', headers: { 'X-Redmine-API-Key': ViewCustomize.context.user.apiKey }, // 作成時はレスポンスのコンテンツが無く、jsonだとエラーとなるのでtextにしておく dataType: 'text', contentType: 'application/json', data: JSON.stringify(issue) }); }; } })
第15回 redmine.tokyo にて、View customize のバージョン1.2.0について発表してきました。
資料はこちら。
www.slideshare.net
発表中に、View cutomizeを知っている人に手を上げてもらったのですが、7、8割くらいの人が手をあげてくれました。うれしくて、ちょっと涙がでました。
他の方の発表も、いろいろ盛りだくさんで面白かったです。また、Twitterなどでやり取りしていた方と会うことが出来たのも良かったです。(ご挨拶しようと思っていて、タイミング逃してしまった方もいるので、また次回に、、)
声をかけていただいて、こうやって参加して発表できて、とても良い1日になりました。スタッフの皆様、ありがとうございました。
社内でも同じこと書いたのですが、こっちでも。
大きなリスト(例えば10万件を超えるようなもの)から一致するものを探す場合、ArrayList#containsを使うと、パフォーマンス的に問題になる場合があります。ArrayList#containsは、先頭からシーケンシャルに見ていくためです。
で、大きなリストを検索する場合には、検索に向いたデータ形式、例えばHashSetに格納することによって、パフォーマンスが大きく改善する場合があります。HashSetはハッシュテーブルを使って値を格納するので、シーケンシャルに見ていくより高速です。
ということで、試したコードです。 10万件の2つのリストを使って、「リスト1からリスト2に含まれるもののみに絞り込む」といったことをやっています。
package com.example; import static org.assertj.core.api.Assertions.assertThat; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.junit.Test; public class LargeListFilterTest { private static final int SIZE = 100000; private static final List<String> SOURCE; private static final List<String> CANDIDATE; static { SOURCE = IntStream.rangeClosed(1, SIZE) .mapToObj(String::valueOf) .collect(Collectors.toList()); CANDIDATE = IntStream.rangeClosed(1, SIZE) .map(x -> x * 2) // 偶数のみに .mapToObj(String::valueOf) .collect(Collectors.toList()); } @Test public void filterWithSetのテスト() { List<String> filterd = filterWithSet(SOURCE, CANDIDATE); assertThat(filterd) .hasSize(SIZE / 2); } @Test public void filterWithListのテスト() { List<String> filterd = filterWithList(SOURCE, CANDIDATE); assertThat(filterd) .hasSize(SIZE / 2); } private <T> List<T> filterWithSet(List<T> source, List<T> candidate) { Set<T> candidateSet = new HashSet<>(candidate); return source.stream() .filter(candidateSet::contains) .collect(Collectors.toList()); } private <T> List<T> filterWithList(List<T> source, List<T> candidate) { return source.stream() .filter(candidate::contains) .collect(Collectors.toList()); } }
パフォーマンスの差はあきらかで、下記のような結果になりました。(あくまで自分のPC上でのパフォーマンスですが、差が大きいのは明確です)
なお、小さいリストだったら、いちいちHashSetに変える必要はないです。逆にHashSetにすることによって遅くなる場合もあります。 (小さければシーケンシャルに見たほうが早い場合も多いです。そもそも小さいと気にするレベルの差にもならないですが…)
このようなボトルネックは、プロファイリングかければすぐに見つかるので、パフォーマンスを厳しく求められたり、思った通りのパフォーマンスが出ていないときは、まずはプロファイリングにかけてみると良いです。 JavaだとFlight Recorderが簡単に使えて便利なので、とても助けられています。(それでこういった問題に気がついたことも多いです)
View customize plugin の v1.2.0 をリリースしました。2年ぶりの新バージョンになります。
今回のリリースには、2つの新規機能と細々とした変更がいくつか入っています。
以降はリリース内容の詳細です。
今までView customizeでは、ヘッダ部分にコードを挿入していました。 それが1.2.0からは、下記の3パターンから選べるようになります。
「Bottom of issue form」は、今まで嵌る人が多かった「チケット入力欄がトラッカーやステータスを変えた際に再構築されてしまい、View customizeでの変更内容がクリアされてしまう」といった問題に対応するためのものです。これを使うと、入力フォームが再構築される際にもコードが再度埋め込まれて実行されるので、今までのような考慮が不要になります。
なお、埋め込み箇所が存在しないページでは埋め込まれないことになるので、Path patternとの組み合わせにご注意ください。
たとえば、「Bottom of issue detail」はチケット詳細でしか埋め込まれないので、Path patternとして.*
で全ページを指定していてもチケット詳細画面でのみ実行されるようになります。(Path patternで正規表現を細かく考えなくて済むので、それはそれで便利かと)
View customizeでは、画面にある情報以外にアクセスするのは大変でした。まったく参照するのが不可な情報も多いですし、出来たとしてもREST API叩いたり、スクレイピングしたり、、と面倒でした。
私自身も、ロールやグループの情報を使って適用する人を区別したいといったことがあり、それら情報にアクセスする手段として、ViewCustomize.context
といったオブジェクトを用意しました。
ユーザに紐づく情報として、ID、氏名以外にも、APIキー、グループ、ロール、カスタムフィールドといった情報に参照することができます。プロジェクトの情報としても識別子やロール、またチケットの情報としてIDを参照できるようにしました。 プロジェクトの識別子やチケットのIDは、今までも画面に存在する情報から取得可能でしたが、利用頻度が多いため、より直感的に書けるように、、ということで加えました。
オブジェクト全体のイメージは下記の通りです。
ViewCustomize = { "context": { "user": { "id": 1, "login": "admin", "admin": true, "firstname": "Redmine", "lastname": "Admin", "groups": [ {"id": 5, "name": "Group1"} ], "apiKey": "3dd35b5ad8456d90d21ef882f7aea651d367a9d8", "customFields": [ {"id": 1, "name": "[Custom field] Text", "value": "text"}, {"id": 2, "name": "[Custom field] List", "value": ["B", "A"]}, {"id": 3, "name": "[Custom field] Boolean", "value": "1"} ] }, "project": { "identifier": "project-a", "name": "Project A", "roles": [ {"id": 6, "name": "RoleX"} ] }, "issue": { "id": 1 } } }
追記@2018-10-20 user.roles は、Redmineのバージョンによってはエラーとなってしまっていたため、1.2.2にて削除しています。上記にも反映しました。
例えばユーザのAPIキーにアクセスするにはViewCustomize.context.user.apiKey
となります。
今回日本語リソースを追加し、ロケールとして日本語を選択しているユーザに対しては、日本語で表示されるようになりました。ずっと英語だったので、今まで使っていた方々には違和感を感じる人も多いかもしれませんが、きっと慣れてもらえると思います。(私も最初は違和感ありましたが、やっと慣れました)
母国語じゃない、、ってだけで、敷居をあげてしまうということは実際あるので、いつかやらなきゃな、、と思っていたのですが、今回Pull requestをもらったのもあって、入れることにしました。今後は他の言語も入れて行きたいと思っています。
一覧系に関することで、下記の2点の修正を行っています。
コメント欄ですが、View customizeの設定が多くなってくると、内容を判断するための情報がコードの先頭部分だけだと厳しくなってきたため、コメント欄として別途設けることにしました。コメントが入力されていなかった場合には、今までどおりコードの先頭部分が表示されます。
他は細かいBugfixなので、詳細は省略します。
今回の新しい機能の使用例を、次回のredmine.tokyoでお話させていただく予定です。
あと、もうすぐリリースされそうなRedime4に対する対応は、Redmine4がリリースされたらすぐに出す予定です。(対応方法は既に確認済みですので、大丈夫かと、、)
人から聞いたチケット番号を元にチケットを開いたり、何かキーワードで検索したり、ってのが良くあるのですが、その際にすぐにRedmineの検索欄に飛べるように、ShortcutKey2URLという拡張機能を使っています。
ShortcutKey2URLは、ショートカットキーを使用してURLを開いたり、移動したり、JavaScriptを実行できる拡張機能です。Chrome版とFirefox版があります。
スタートアップキーであらかじめ設定しておいた動作の一覧を表示し、次のキーでその動作を実行するような動きとなっています。
キーとして使用できる文字は1文字に限定されません。複数文字として設定しておくことが可能です。ShrotcutKey2URLは、キーとして連続して入力された文字から、対象が1つに絞り込まれた時点でその動作を実行します。
動作として設定できるものには、下記のようなものがあります。
ということで、Redmineの検索欄に素早く移動できるようにショートカットキーを設定します。
新しいタブでRedmineのページを開いて、その後にJavaScriptで検索欄にフォーカスを当てます。
document.getElementById('q').focus();
こんな感じの設定になります。(http://192.168.33.11/ がRedmineのURL)
動作イメージは以下のような感じです。
ShortcutKey2URLは他にもいろいろ使える場面があると思うので、ぜひご利用ください!!
ずっと行きたかったRUANNのライブにやっといけました。嫁さんと一緒での参戦でした。
半分ぐらいの曲がギターでの弾き語りで、他の曲はダンスしながらと、とても元気なパフォーマンスを見せてくれました。
洋楽アーティストのライブみたいな感じをイメージしていたのですが、みんなで歌うって感じじゃなくて、みんな聴き入っている、、っていうのが多かったです。実際ギターの弾き語りはすごくよくて、自分も聴き入ってしまいました。
オリジナル曲の歌詞は、若い子向けかなってのもあって、ちょっと自分の世代(40代..)だと共感というより、娘はこんな感じなのかな(長女とRUANNは1歳違い)、、と思いながら聴く曲もあります。アンコールのときのMCは、実際に娘のことを考えながら聞いてしまい、ちょっと涙ぐんでしまいました。ほんと頑張って欲しい。応援してる!
洋楽のカバーもいくつか入れてきましたが、Lady Gaga の「Born This Way」は特に良かったです。英語の歌詞で力強く歌う声がすごくいい。RUANNのことを好きになったのも、この力強い声と、ギター1本でのパフォーマンスでした。
メジャーデビューも決まっているので、これからいろんな人がRUANNの存在に気がつくと思います。若いのに、、とかじゃなく、年齢関係なくすごいアーティストなので、これからのパフォーマンスがとても楽しみです。