LoginSignup
2
2

More than 5 years have passed since last update.

Mantisで重複先課題総数を課題一覧とExcel エクスポートに表示する

Posted at

概要

Mantisの検索画面およびそこからのExcel出力時に、課題の関連付けで、重複先に設定された課題数を整数値で表示します。

このカスタマイズは、通常用意されているカスタマイズ方法とは逸脱しており、プラグインも使用しません。
すなわち、coreのphpファイルを複数編集し、Mantis全体に影響を及ぼします。
ON・OFF機能も1ありません。
今後のバージョンアップの追従に悪影響を及ぼすと考えてもらっていいと思います。

対象バージョンは1.2.17となります。
現在開発中の1.3.x系統とは細部が異なり、同様の変更ができるとは限りません。2

再度触る場合のために調査過程から書いています。

背景(読み飛ばしOK)

各所から上がってくるバグ報告の、改修優先度を決める際の指標の一つとして、同様のバグ報告数を使うとする。
報告は同じバグでも環境ごとに細部がことなるため、先着の課題にコメントで追記していくのは不十分な可能性がある。(加えて重複数を手動でカウントしなければならない(コメント数≠重複数))
そのため、重複の課題登録を許可し、重複が発見された場合は、関連機能を使い、重複元,重複先関係を構築する。

次期改修課題の決定の際は、個々の課題を確認するのではなく、課題のExcel出力から、各課題の概要(タイトル)と優先度(標準機能:別ルールで決定)と報告数(発生数、重複数)を俯瞰し協議する。

関係は標準機能では表示項目に設定できないため、手動でカスタマイズを行い、重複先数のみを集計し、新規列に表示したい。

注意

いろいろな箇所をいじります。
数自体は多くはないのですが、その関連性が見えにくいというか、カスタマイズ中はあっちいったりこっちいったりで混乱します。多分数日経ったら全貌を忘れますし、カスタマイズ中も把握していたというと嘘になります。
プラグインで一から出力機能を作ったほうがまだいい感じに思えます。
できるだけメモをとりながら、なぜここに書いたか、こことこことは似ているのになぜ違うかを把握したほうがいいでしょう。

:one: 課題一覧の出力用SQLの確認

課題一覧とは以下の画面のこと(DEMOから拝借)
mantis serach.png

「要約」の隣に新たに列を追加します。

この一覧をどうやって作っているのかを追うと、URLの/view_all_bug_page.phpから辿り、/view_all_inc.phpでHTML出力している。しかし、ループする課題の変数$rowsは戻って/view_all_bug_page.phpから

$rows = filter_get_bug_rows( $f_page_number, $t_per_page, $t_page_count, $t_bug_count, null, null, null, true );

で取得されている。
$rowsをデバッグすると、課題情報のオブジェクトが配列で入っているので、DBの取得結果と推測する。

filter_get_bug_rows()は/core/filter_api.phpの1037行目から長々と書かれているが、SQLの組み立てとだけ認識して末尾から読む。

filter_get_bug_rows()return前

    $t_result = db_query_bound( "$t_select_string $t_from_string $t_join_string $t_where_string $t_order_string", $t_query_clauses['where_values'], $p_per_page, $t_offset );
    $t_row_count = db_num_rows( $t_result );

    $t_id_array_lastmod = array();
    for( $i = 0;$i < $t_row_count;$i++ ) {
        $t_row = db_fetch_array( $t_result );
        $t_id_array_lastmod[] = (int) $t_row['id'];
        $t_rows[] = $t_row;
    }

    return filter_cache_result( $t_rows, $t_id_array_lastmod );
}

明らかにdb_query_bound()がクエリ実行なので、引数をデバッグ

クエリ変数デバッグ

var_dump($t_select_string); // "SELECT DISTINCT mantis_bug_table.*"
var_dump($t_from_string);   // " FROM mantis_bug_table"
var_dump($t_join_string);   // "JOIN mantis_project_table ON mantis_project_table.id = mantis_bug_table.project_id"
var_dump($t_where_string);  // "WHERE mantis_project_table.enabled = ? AND ( mantis_bug_table.project_id = 5 ) AND ( ( mantis_bug_table.status in (?, ?, ?, ?, ?) ) ) "
var_dump($t_order_string);  // " ORDER BY mantis_bug_table.sticky DESC, mantis_bug_table.last_updated DESC, mantis_bug_table.date_submitted DESC"
var_dump($t_query_clauses['where_values']); // array(1,10,50,60,70,80)

合わせると


SELECT DISTINCT mantis_bug_table.* FROM mantis_bug_table JOIN mantis_project_table ON mantis_project_table.id = mantis_bug_table.project_id WHERE mantis_project_table.enabled = 1 AND ( mantis_bug_table.project_id = 5 ) AND ( ( mantis_bug_table.status in (10, 50, 60, 70, 80) ) )  ORDER BY mantis_bug_table.sticky DESC, mantis_bug_table.last_updated DESC, mantis_bug_table.date_submitted DESC;

フィルターやプロジェクト選択で細部は変わると思われるが、そこは問題無いはず。
JOINやWHEREが綺麗に変数分けされているので、この変数群にうまく重複数を追加するようにSQLを足せばいいはず。

:two: 重複数を計算するSQL

mantisの関係はDBのmantis_bug_relationship_tableに入っている。構造はこんな感じ。


mysql> show columns from mantis_bug_relationship_table;
+--------------------+------------------+------+-----+---------+----------------+
| Field              | Type             | Null | Key | Default | Extra          |
+--------------------+------------------+------+-----+---------+----------------+
| id                 | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| source_bug_id      | int(10) unsigned | NO   | MUL | 0       |                |
| destination_bug_id | int(10) unsigned | NO   | MUL | 0       |                |
| relationship_type  | smallint(6)      | NO   |     | 0       |                |
+--------------------+------------------+------+-----+---------+----------------+
4 rows in set (0.10 sec)

幸いにも、重複元 重複先関係は、source_bug_iddestination_bug_idで表されている(重複先がsource_bug_idに現れることはない)。
よって、単純に、relationship_type重複元source_bug_idをグループ化してやれば良い。

以下で求められる(ちょっとcount(*)とかあやしい)。

課題ごとの重複先数を集計するSQL

mysql> select source_bug_id, count(*) as count from  mantis_bug_relationship_table where relationship_type = 0 group by source_bug_id;
+---------------+-------+
| source_bug_id | count |
+---------------+-------+
|           169 |     2 |
|           324 |     1 |
+---------------+-------+
2 rows in set (0.00 sec)

これを、:one:の変数に組み込む。

:three: 課題一覧の出力に重複数を追加する

SQLに疎いのでかなり悪戦苦闘しましたが、JOINで以下な感じになりました。


    // カスタマイズ
    $t_select_string = $t_select_string . ', COALESCE(mantis_bug_relationship_table.count, "") as duplicate_count';
    $t_join_string = $t_join_string . ' LEFT JOIN (select source_bug_id, count(*) as count from  mantis_bug_relationship_table where relationship_type = 0 group by source_bug_id) as mantis_bug_relationship_table ON mantis_bug_table.id = mantis_bug_relationship_table.source_bug_id';

    $t_result = db_query_bound( "$t_select_string $t_from_string $t_join_string $t_where_string $t_order_string", $t_query_clauses['where_values'], $p_per_page, $t_offset );

単純にLEFT JOINで副問い合わせを結合し、重複が無い行にはNULLが入るのでCOALESCE()で空文字に初期化します。初期化は0でもよかったですが、コメント数などを参考に空に。ここでCOALESCE()を使ったことでasで名前付け出来たのが、もしかしたらよかったかもしれません。

再度$rowsvar_dumpし、結果のオブジェクトにduplicate_countが増えていればOKです。(まだHTMLには反映されません。)

:four: 課題一覧に表示させる

課題一覧に表示させるには、表示項目管理の表示項目の欄にカラム名を追加します。(管理でもアカウント毎でも)
詳しくはMantisの使い方(検索とCSV出力の表示項目) - Symfoware
課題一覧なので、「表示項目」にduplicate_countを追加したいのですが、そのままだと「全項目」に存在しないと弾かれる。「全項目」はreadonlyで編集不可という状態です。

「全項目」にduplicate_countを追加する。

「表示項目管理」画面のURLから/manage_config_columns_page.phpを調査し、何所から全項目を引っ張ってきているのかを調べます。
短いですね。共通部分と画面部分の読み込みのみですので、肝心の/manage_columns_inc.phpを調べます。
怪しいのは48行目の$t_columns = columns_get_all( $t_project_id );ですね。その後のHTMLの作り方から見てもこれです。
columns_get_all()は/core/columns_api.phpの175行目です
戻り値は配列なのでreturn前に追加しちゃいます。

columns_get_all()

    $t_columns[] = 'duplicate_count'; // 無理矢理カラム追加
    return $t_columns;
}

ブラウザから「全項目」の最後に, duplicate_countが増えていればOKです。
「表示項目」にも, duplicate_countが追加できるようになったので、追加しましょう。

duplicate_countをソート可能にする

課題一覧を見ると画像の様に列が増えているはずです。

duplicate_count.png

他のリンクになっている列名と同じく、リンクをクリックすると、ソートしようとします。
また、検索フィルターの並び順にも追加されています。

filter.png

ですが、これらを使ってduplicate_countでソートすると、SQLエラーになります。
原因は、カスタマイズ項目でもプラグイン設定されてもいない項目=デフォルト項目のソート時に、mantis_bug_tableをつけようとするからです。
つまりORDER by mantis_bug_table.duplicate_countでソートしようとするのですが、duplicate_countは副問い合わせ結果なので、エラーになります。
命名時にas mantis_bug_table.duplicate_countと横着しようとしましたが、これも怒られました。当然ですね。

なので、duplicate_countでのソート時はmantis_bug_tableを付けないようにする必要があります。

ORDER BYはどこで作成されているか。
重複数の追加のときにすでに出ています。


var_dump($t_order_string);  // " ORDER BY mantis_bug_table.sticky DESC, mantis_bug_table.last_updated DESC, mantis_bug_table.date_submitted DESC"

$t_order_stringは/core/filter_api.phpの2048行目$t_query_clauses['order']から組み立てられており、
$t_query_clauses['order']で既にテーブル名は付加済み。

$t_query_clauses['order']は/core/filter_api.phpの2033行目で、filter_get_query_sort_data()$t_query_clausesを渡し、そこで['order']を追加した上で$t_query_clausesに上書きします。

filter_get_query_sort_data()は同じく、/core/filter_api.phpの892行目

で、よく見かける3種類の分類をしています。

  1. カスタムフィールド用
  2. プラグインフィールド用
  3. その他デフォルト用

デフォルト処理が以下


            # standard column
            } else {
                $t_sort_col = "$t_bug_table.$c_sort";

                # when sorting by due_date, always display undefined dates last
                if( 'due_date' == $c_sort && 'ASC' == $c_dir ) {
                    $t_sort_due_date = "$t_sort_col = 1";
                    $p_query_clauses['select'][] = $t_sort_due_date;
                    $t_sort_col = "$t_sort_due_date, $t_sort_col";
                }

                $p_query_clauses['order'][] = "$t_sort_col $c_dir";
            }

$t_sort_col = "$t_bug_table.$c_sort";でテーブル名が付加されているので、elseの前にelse ifを追加して、duplicate_count専用の処理を追加します。


            } else if($c_sort == 'duplicate_count') {
                $p_query_clauses['order'][] = "$c_sort $c_dir";

            # standard column
            } else {

これでSQLエラーがでなくなり、ソートが可能になりました。

duplicate_count_sort.png

:five: Excel エクスポートに表示させる

Excel エクスポートに使用するカラムは、表示項目管理の「Excel 項目」です。課題一覧と同じく、ここにも, duplicate_countを追加し、更新します。

まずは出力

個人的にはHTMLより単純なので、これでカラムが増え、あとは和訳だけだと思っていたのですが…ファイルが出力されなくなる。
Excel エクスポート機能は/excel_xml_export.php
よくわからないので/excel_xml_export.phpini_set( 'display_errors', 1 );を書いて、再読み込み。今度はファイル保存のダイアログが出て、開いたxmlには

Fatal error: Call to undefined function excel_format_duplicate_count() in /mantisbt-1.2.17/excel_xml_export.php on line 84

84行目を見ると

$t_function = 'excel_format_' . $t_column;
echo $t_function( $t_row );

と、どうやらカラム毎に値の出力用関数が必要なようなので、excel_format_duplicate_count()を作成します。
場所は/core/excel_api.phpです。

print_column_title_duplicate_countのときの様に、他の関数をコピーして…


function excel_format_duplicate_count( $p_bug ) {
    return excel_prepare_string( $p_bug->duplicate_count );
}

です。excel_prepare_string()& < >のHTML(XML?) エンティティ化で、整数値と空文字のduplicate_countには不要なのですが…やっぱりお約束でつけておきます。

これでxlsの出力が可能になりました。値は正常に反映され、1行目はdulicate_countになります。

xls_before.JPG

項目名を変更する

では最後にdulicate_countに項目名をつけ日本語にします。

あやしいのはexcel_xml_export.phpの62行目
echo excel_get_titles_row();

定義は/core/excel_api.phpの107行目

カラムごとに$t_ret .= excel_format_column_title( column_get_title( $t_column ) );としています。

excel_api.phpexcel_format_column_title()はカラム名をExcel用のXMLにくるんでいるだけなので、/core/columns_api.phpcolumn_get_title()ですね。

これ、結構custom_function_default_print_column_title()と似ていて、の割にはオーバーライド不可だったりして、もやっとしてしまいました。
column_get_title()は同様にカスタム、プラグイン、その他で処理が分かれていますが、if elseを使わずガードというか関数から早く返す3が実践できてますね。
(このガードが使うべき特殊な例外であるかは別問題ですが、個人的にはelse(ネスト)は極力減らしたいです。)
閑話休題。

で、カスタムでもプラグインでむにゃむにゃでもないので、switch文に入ります。そこでcase 'bugnotes_count'の様に


case 'duplicate_count':
            return '重複している課題';

といれれば完成しそうなんですが…
現状入っている


default:
    return lang_get_defaulted( $p_column );

で、恐らく言語ファイルから訳を取ってきているように見えます。
カスタマイズの気楽さが
言語ファイルへの追加 >> coreのphpファイルの編集
なので、caseの追加はせず、言語ファイルに追加してみます。
(lang_*系の処理は読み飛ばしたんですが、まぁやりたいことはどこでも同じだろうということで…)

対象ファイルは/lang/strings_japanese.txt

見た感じルールが$s_カラム名 = 訳なので、$s_duplicate_count = '重複している課題';と追記して保存。

これでExcel エクスポートも日本語になりました!

xls_after.JPG

ついでにデフォルト処理に引っ張られ、課題一覧も日本語化されたので、これにて完成です。

欠番:項目名を変更する

欠番説明:このセクションは本来、「全項目」にduplicate_countを追加する。の次にありました。
差し替え項で説明しているかもしれませんが、以下の作業を行っても、フィルターからduplicate_countのソートが可能であり、同様のエラーが発生します。
そこで、差し替え項ではソートが可能になるようにしましたが、項目名の変更というカスタマイズの手法として、欠番扱いで残しておきます。


…の前に!この段階の課題一覧の重複数表示は恐らくこんな感じなはず。

duplicate_count.png

(全部重複無しなので空文字""が入っています(悪い例画像))

で、カラム名がリンクになっています。クリックすると、ソートしようとします。ですけど、duplicate_countは無理矢理つけたので、当然失敗し、エラーになります。そして
duplicate_countをソート対象に選択したという記録が残り、以後検索画面を表示するたびにエラーになります。
別のカラムをソート対象に設定し直そうとしても、そもそもそのための検索画面がエラーで表示されません。
ログアウトしても無駄です。詰みです。

というぐらい焦りましたが、「表示項目」から一旦, duplicate_countを削除すれば大丈夫です。

重複数のソートができればそれにこしたことはないですが、ちょっとキツイので、コメント数などと同様にソート不可にしたいです。

では、カラム名がどうやって出力されているか追います。
/view_all_inc.phpの163行目からがカラム名の出力ですね。
helper_call_custom_function()を使っているということは、関数をオーバーライドできるのですが…オーバーライドの仕組みを飛ばし、実際に呼ばれるデフォルトのcustom_function_default_print_column_title()を見ます4
中身は大きくif elseでカスタム項目とその他に分かれていて、今回はその他ですね。

custom_function_default_print_column_title()のelse

    } else {
        $t_plugin_columns = columns_get_plugin_columns();
        $t_function = 'print_column_title_' . $p_column;
        if( function_exists( $t_function ) ) {
            $t_function( $t_sort, $t_dir, $p_columns_target );
        } else if ( isset( $t_plugin_columns[ $p_column ] ) ) {
            $t_column_object = $t_plugin_columns[ $p_column ];
            print_column_title_plugin( $p_column, $t_column_object, $t_sort, $t_dir, $p_columns_target );
        } else {
            echo '<td>';
            print_view_bug_sort_link( column_get_title( $p_column ), $p_column, $t_sort, $t_dir, $p_columns_target );
            print_sort_icon( $t_dir, $t_sort, $p_column );
            echo '</td>';
        }
    }

で、その中でも3種類に別れ、

  1. print_column_title_カラム名()の関数があればそれを実行
  2. プラグインでむにゃむにゃしているならそれ
  3. それ以外はデフォルトでソートリンク付きHTML

のようです。
先の画像の例は3のデフォルトのようです。
プラグインはよくわからないし、せっかく仕組みを用意していくれているので、今回は1のprint_column_title_duplicate_count()を作成します。
(custom_function_default_print_column_title()はせっかくカスタマイズできる仕様なのにしませんでした。custom_function_override_print_column_title()を作って、duplicate_countのときはこう!というのを追加するのもいいと思います。)

といっても、適当なprint_column_title_*()を拾ってさらっと改変


function print_column_title_duplicate_count( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
    echo '<td> 重複先数 </td>';
}

場所は/core/columns_api.phpです。
引数はなくてもいいと思いますが、お約束で。
文字の位置は他の雰囲気的に<td class="left">などで出来そうカモ。

これで課題一覧での出力はひとまず完成です。

mantis serach2.png

日本語でソート無しのカラムになりました。


  1. 今回の実装では(configで切り替えは出来そうだと思っています) 

  2. 1.3.xのコードをしっかり読んでいませんし、そもそも1.2.17も把握しきれていません… 

  3. リーダブルコードにあったはず 

  4. custom_function_override_print_column_title()があればそちらが実行されます。helper_call_custom_function()の仕組みです。 

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2