SonarQube ウィジェットプラグインを作成する

SonarQube を使用すると、コードに対するさまざまな数値が取得できるので、プロジェクトの状態を可視化するのに非常に役立ちます。

Web API も提供されているため、これを使用するとデフォルトで提供されている視覚化ツール以外の視点からも、様々にプロジェクトを見通すことができます。
この API を使用してカスタムウィジェットを追加したりすると、SonarQube のダッシュボードをひとつ見るだけで、様々な情報を一度に把握できて便利です。

ところが、SonarQube プラグインの開発に関する情報は非常に少なく、公式でも開発ドキュメントまでは手が十分に回っていないようで、デッドリンクが放置されている始末、ネットを探しても断片的な情報ばかり。
というわけで、前置きが長くなりましたが、簡単なウィジェットプラグインの開発手順を、ここに書き残しておきます。

# 環境

* ORACLE Java 1.8.0_45
* Apache Maven 3.3.3
* SonarQube 5.2

```bash:terminal
$ mvn --version
Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T20:57:37+09:00)
Maven home: /usr/local/Cellar/maven/3.3.3/libexec
Java version: 1.8.0_45, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.10.3", arch: "x86_64", family: "mac"
```

# プロジェクトを作成
ドキュメントに倣い、プロジェクトを作成していきます。
部分的に公式ドキュメントでは触れられていない部分もあるため、補足しつつ紹介します。

http://docs.sonarqube.org/display/DEV/Extend+Web+Application

## ディレクトリ構成
一般的な Maven を使用したプロジェクトと同じです。

```
{plugin/root/directory}/
|
+-- src/
|  |
|  --- main/
|     |
|     +-- java/
|     |  |
|     |  --- {my.plugin.packagename}/
|     |     |
|     |     --- {MyPlugin}.java
|     |         {MyWidget}.java
|     |
|     --- resource/
|        |
|        +-- {my/plugin/packagename}/
|        |  |
|        |  --- {my_widget}.html.erb
|        |
|        --- org/
|           |
|           --- sonar/
|              |
|              --- l10n/  *Localization
|                 |
|                 --- {myWidget}.properties  *多言語対応用
|
--- pom.xml
```

## pom.xml
POM は、SonarQube プラグイン用の記述が必要な箇所になります。
なお、公式ドキュメントでは、`pluginDescription` の入力を必須としていませんが、これが入力されていないとビルドが通りません。(必須です。)

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>{my.group.id}</groupId>
    <artifactId>{my-artifact-id}</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- this is important for sonar-packaging-maven-plugin -->
    <packaging>sonar-plugin</packaging>

    <dependencies>
        <dependency>
            <groupId>org.sonarsource.sonarqube</groupId>
            <artifactId>sonar-plugin-api</artifactId>
            <!-- minimal version of SonarQube to support. Note that the groupId is "org.codehaus.sonar" for versions lower than 5.2 -->
            <version>5.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
                <artifactId>sonar-packaging-maven-plugin</artifactId>
                <version>1.15</version>
                <extensions>true</extensions>
                <configuration>
                    <!-- the entry-point class that extends org.sonar.api.SonarPlugin -->
                    <pluginClass>{my.plugin.packagename.MyPlugin}</pluginClass>

                    <!-- advanced properties can be set here. See paragraph "Advanced Build Properties". -->
                    <pluginDescription>{My plugin description}</pluginDescription>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
```

## プラグインクラス
`pom.xml` で、`pluginClass` に設定したプラグインクラスが呼び出され、プラグインクラスで `#getExtensions()` により返却されるクラス群が、拡張として認識される。

```java:{MyPlugin}.java
package {my.plugin.packagename};

import org.sonar.api.SonarPlugin;

import java.util.Arrays;
import java.util.List;

public class {MyPlugin} extends SonarPlugin {
    @Override
    public List getExtensions() {
        return Arrays.asList({MyWidget}.class);
    }
}
```

## ウィジェットクラス
このクラスでは、ウィジェットに関するいくつかの定義をおこなう。
この定義は、ダッシュボード上へウィジェットを配置したり、表示したりするのに使用される。
`@WidgetProperty` で定義したプロパティは、テンプレートファイルから `key` の値を使用して参照可能になります。

```java:{MyWidget}.java
package {my.plugin.packagename};

import org.sonar.api.web.*;

@UserRole(UserRole.USER)
@Description("{Desc}")
// WidgetCategory は、以下から選択
// "Filters", "History", "Hotspots", "Issues", "Technical Debt", "Tests"
@WidgetCategory({"{Category1}", "{Category2}", ...})
// WidgetProperty は、定義しなくてもいいし、いくつ定義してもいい
@WidgetProperties({
        @WidgetProperty(
                key = "{PropertyKey}",
                description = "{Property Desc}",
                defaultValue = "{propDefaultValue}"),
        @WidgetProperty(...), ...        
})
public class {MyWidget} extends AbstractRubyTemplate implements RubyRailsWidget {
    @Override
    protected String getTemplatePath() {
        return "{/my/plugin/packagename/my_widget}.html.erb";
    }

    public String getId() {
        return "{pluginId}";
    }

    public String getTitle() {
        return "{Plugin Title}";
    }
}
```

定義した値は、それぞれ以下のようにプラグイン選択時に使用されている。

<img src="https://qiita-image-store.s3.amazonaws.com/0/47993/12b82953-6c9a-e8b9-47e7-ebd601385a5d.png" alt="Widget mapping 1" title="Widget1" width="65%" />

<img src="https://qiita-image-store.s3.amazonaws.com/0/47993/29c17fab-0792-6517-d095-e6d5be3dfeba.png" alt="Widget mapping 1" title="Widget1" width="30%" />

<img src="https://qiita-image-store.s3.amazonaws.com/0/47993/8e46fac6-83f6-c2ab-1824-b1c47637080f.png" alt="Widget mapping 1" title="Widget1" width="75%" />


## ウィジェットテンプレート
ウィジェットプラグインの場合、実装すべき機能の多くは実はここに集まっている。
既存ウィジェットのコードを読んでみると、多くのウィジェットが、Web API と Javascript を使用して、ほとんどの機能をここで実装していた。
今回もそれに倣い、作成することとする。

なお、テンプレートエンジン周りには、Ruby (JRuby) が使用されているので、ディレクティブなどの記法を調べる場合は、Ruby や erb あたりを調べてみると良い。

```erb:{my_widget}.html.erb
<% if has_role?(:user, @project) %>
    <style>
      /* CSS */
    </style>
    <div id="widget-<%= widget.id.to_s -%>" class="hanger-widget">
      <table class="data">
        <thead>
        <%# src/resource/org/sonar/l10n/{myWidget}.properties で定義したキー名を使用できる %>
        <th><h3><%= message('widget.{pluginname}.name') -%></h3></th>
        <th class="right"><h3><%= message('widget.{pluginname}.label1') -%></h3></th>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var widget<%= widget.id.to_s -%>Scope = function () {
        var projectKey = '<%= h(@project.key) -%>';
        var widgetProperty = '<%= h(widget_properties["{PropertyKey}"]) -%>';
        
        var $ = jQuery;
        $.ajax({
          url: '/api/issues/search?projectKeys=' + projectKey
        }).done(function(data) {
          var $widget = $('#widget-<%= widget.id.to_s -%>');
          
          // something to do
          
        }).fail(function ($xhr, textStatus, errorThrown) {
          console.log(textStatus);
          console.log(errorThrown);
        });
      };

      widget<%= widget.id.to_s -%>Scope();
    </script>
<% end %>
```

`$.ajax` で、ウィジェットがインストールされる SonarQube サーバーの Web API から、情報を取得し、画面部品を組み立てていくという、ごくごく一般的な Javascript のコードになる。
SonarQube のプラグインは、ここまでで紹介したお膳立てさえ済んでしまえば、Javascript を使うだけで、概ね つまづくことなく作成できてしまう。

# まとめ
ここまでで、ウィジェットプラグインを作成するための簡単な手順を紹介したが、ウィジェットの作成程度であれば、Javascript による HTML の組み立て程度で済んでしまう。
非常に簡単にウィジェット開発ができる反面、公式ドキュメントの整備がイマイチなために、そこに至るまでに、細かいところでアレコレつまづいてしまうことがあるので、このメモが助けになればと思う。

最後に、サンプルプロジェクトとして、作成したウィジェットを晒しておく。
https://github.com/yo1000/hanger.git

コミッタごとに、プロジェクトに現在残っているコードへのコミット数を集計するウィジェット。
<img width="653" alt="スクリーンショット 2015-12-29 6.39.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/47993/68a55f9d-fffe-f5f1-a772-6cf324db037e.png">

コミッタごとに、負債を集計するウィジェット。
<img width="645" alt="スクリーンショット 2015-12-29 21.36.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/47993/3783186f-492f-96c7-f9f1-356cb7c20339.png">

コミッタごとに、コミット数に対する負債の割合を集計するウィジェット。
(Ratio が高いほどコミットの質が低い。)
<img width="647" alt="スクリーンショット 2015-12-29 21.31.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/47993/e96c6059-802a-052d-99ff-1251d04fa760.png">

# 参考
http://docs.sonarqube.org/display/DEV/Developing+Plugins
http://docs.sonarqube.org/pages/viewpage.action?pageId=2392181#WebService/api/issues-GetaListofIssues
https://github.com/SonarSource/sonar-widget-lab/tree/master/src/main/java/org/codehaus/sonar/plugins/widgetlab
https://github.com/SonarSource/sonarqube/tree/0ae5ddd039e29094eee818370fe34810d3a225f7/server/sonar-server/src/main/resources/org/sonar/server/dashboard/widget