Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

RedmineのView Customizeを用いたJavaScript OSSの組み込み

More than 1 year has passed since last update.

目的

Github上には大量のJavaScript OSSが公開されており、それらをRedmineに組み込めれば機能を大きく拡張できるようになる。

redmine_view_customizeプラグインはv2.1.0から従来のCSS/JavaScriptに加えHTMLを埋め込めるようになった。
この機能とプロジェクトの添付ファイルを利用してRedmineのシステム管理者権限のみで(つまりサーバーへログインせずに)JavaScript OSSを組み込める。

この記事では例としてJavaScriptのOSS「BigPicture.js」を組み込み、チケットの添付ファイルの画像/音声/動画のプレビュー機能を強化する

Redmine 4.0.3とView Customize v2.1.0で確認した。

スクリプト配置用プロジェクトの作成

スクリプト配置用プロジェクトを作成する。ここでは「ViewCustomizeVault」とした。

このプロジェクトの「ファイル」メニューにJavaScriptを配置する。
BigPicture.jsでは BigPicture.min.js を添付する。

Greenshot 2019-05-17 18.36.46.png

View Customizeの設定

BigPicture.min.jsとビジネスロジックを埋め込む。

<script>タグのsrc属性には添付した BigPicture.min.js のダウンロードURLを入れる。

このコードでは画像/音声/動画を内部で判別して該当するプレビューAPIを呼び出している。
実際に表示可能かどうかはブラウザ次第になるものの、ブラウザのアドオンやプラグイン側でプレビュー機能を拡張することで追従しやすくなる。

  • Path pattern: /issues/[0-9]+
  • Type: HTML
<script src="/redmine/attachments/download/1/BigPicture.min.js"></script>
<script>
window.addEventListener('load', function() {
    const mimeTypes = {
        "apng": "image/apng",
        "bmp": "image/x-ms-bmp",
        "gif": "image/gif",
        "jpeg": "image/jpeg",
        "jpg": "image/jpeg",
        "ktx": "image/ktx",
        "pic": "image/x-pict",
        "png": "image/png",
        "psd": "image/vnd.adobe.photoshop",
        "rgb": "image/x-rgb",
        "svg": "image/svg+xml",
        "svgz": "image/svg+xml",
        "tfx": "image/tiff-fx",
        "tga": "image/x-tga",
        "tif": "image/tiff",
        "tiff": "image/tiff",

        "aac": "audio/x-aac",
        "aif": "audio/x-aiff",
        "aiff": "audio/x-aiff",
        "m3a": "audio/mpeg",
        "m4a": "audio/x-m4a",
        "mid": "audio/midi",
        "midi": "audio/midi",
        "mp3": "audio/mpeg",
        "mp4a": "audio/mp4",
        "oga": "audio/ogg",
        "ogg": "audio/ogg",
        "wav": "audio/x-wav",
        "weba": "audio/webm",
        "wma": "audio/x-ms-wma",

        "asf": "video/x-ms-asf",
        "asx": "video/x-ms-asf",
        "avi": "video/x-msvideo",
        "h264": "video/h264",
        "jpgv": "video/jpeg",
        "m2v": "video/mpeg",
        "m4v": "video/x-m4v",
        "mov": "video/quicktime",
        "mp4": "video/mp4",
        "mp4v": "video/mp4",
        "mpeg": "video/mpeg",
        "mpg": "video/mpeg",
        "mpg4": "video/mp4",
        "ogv": "video/ogg",
        "webm": "video/webm",
        "wmv": "video/x-ms-wmv",
        "wmx": "video/x-ms-wmx"
    };

    var click_OpenThumbnail = function(e){
        const ext = e.target.getAttribute('data-dp').split(".").pop();
        const mime = mimeTypes[ext];
        if(/^image\//.test(mime)){
            BigPicture({el: e.target, imgSrc:e.target.getAttribute('data-dp')});
        }
        else if(/^audio\//.test(mime)){
            BigPicture({el: e.target, audio:e.target.getAttribute('data-dp')});
        }
        else if(/^video\//.test(mime)){
            BigPicture({el: e.target, vidSrc:e.target.getAttribute('data-dp')});
        }
        else{
            BigPicture({el: e.target, iframeSrc:e.target.getAttribute('data-dp')});
        }
        e.preventDefault();
    }

    document.querySelectorAll('a:not(.icon-only)[href*="/attachments/"]').forEach(link => {
        const src = link.pathname.replace("/attachments/", "/attachments/download/") + "/" + link.text;
        const thumb = link.pathname.replace("/attachments/", "/attachments/thumbnail/")+"/200";

        link.setAttribute('data-dp', src);
        link.setAttribute('data-caption', link.text);
        link.addEventListener('click', click_OpenThumbnail);

        // create thumbnail
        const ext = src.split(".").pop();
        const mime = mimeTypes[ext];
        if(/^image\//.test(mime)){
            let img = document.createElement("img");
            img.src = thumb;
            img.alt = link.text;
            img.style = "max-width: 100px; max-height: 100px;";
            img.setAttribute('data-dp', src);
            img.setAttribute('data-caption', link.text);
            img.addEventListener('click', click_OpenThumbnail);
            link.insertBefore(img, link.firstChild);
        }
        else if(/^video\//.test(mime)){
            let video = document.createElement("video");
            video.src = src;
            video.style = "max-width: 100px; max-height: 100px;";
            video.setAttribute("preload", "metadata");
            video.setAttribute('data-dp', src);
            video.setAttribute('data-caption', link.text);
            video.addEventListener('click', click_OpenThumbnail);
            link.insertBefore(video, link.firstChild);
        }
    })
});
</script>

実行結果

Redmineファイルプレビュー機能の強化.gif

メリット

  • Redmineサーバーに入る必要がないため、サーバーのログイン権限が無い人でも組込みや更新ができる。
  • GitやSVNの運用フローや更新の仕組みを考える必要が無いため手頃に実現できる。

デメリット

OSSのソースコードは上書き更新できない

Redmineの添付ファイルは上書きできないためJavaScriptのOSS更新時には *.js の上げ直しと、View Customize側での<script>タグのsrc属性の更新が必要になる。頻繁に更新されるものには不向き。

ページの読み込み負荷が上がる

Redmineの添付ファイルはブラウザキャッシュされない設定になっているらしく、リクエスト事にファイルの通信が発生してしまう

特に従量課金のクラウドサーバーやパフォーマンスにシビアな大規模Redmineでは、巨大なJavaScriptや補助のデータファイルを大量に組み込みたい場合に問題になる可能性がある

そういったケースではこの添付ファイルを利用した方法は使わずに、ブラウザがファイルをキャッシュするようにCDNにファイルを置いたり、ファイルをGit等で管理して更新はJenkins等で自動検知してRedmineの /public ディレクトリ下へ展開してそのURLを組み込む等の仕組みが必要になる。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away