ラック
Home > CMS > 記事 > 2015年11月 > WPを使って簡易Webバグ管理システムを構築する

WPを使って簡易Webバグ管理システムを構築する

カテゴリ: WordPress

1.経緯

プログラマの永遠の宿敵・バグ。

バグを産まない完全無欠なプログラマはそうそういないと思いますので、平凡プログラマーは何とかして自らが産んだバグを駆逐していかなければなりません。

かつて寝ずの番をして三尸の虫を封じ込めようとした庚申講のように――

 

とはいえ、プログラムの規模が巨大になったり、中身が複雑になったり、突然の仕様変更で横槍を突き刺されたり、納期を縮めろと圧迫されたり…etcetcといった数多の要因でバグは量産されてしまいがちです。

何とかしてこれらのバグを野放しにすることなく退治できないものか。

 

世の中は便利なもので、そうしたバグを管理するためのシステムというのがたくさんあります。フリーから有償まで様々。

フリーのもので注目していきますと、LAMP環境ではBugzillaやMantis、RubyではRedMine、PythonではTracといったところが有名どころでしょうか。

これらのうち、自分に合ったものを選択して使いこなせば効率が上がりそうです。

個人的にはLAMPで動くMantis、CandyCaneを試してみました。

Mantisは「虫(バグ)を捕食するカマキリ」ということでそのネーミングになったそうなので非常にときめくものを感じました。

しかし、MantisもCandyCaneもどうもしっくりきませんでした。

というのも、今までこうしたツールを使ったことがなかったので、まず概念が馴染めなかったのです。

加えて1つのチケットを発行するだけでも入力項目が多く、書き込む気力がなくなりました。

その上、プロジェクトごとに管理するに当たっても画面の遷移が分かりづらい・見づらい…etc。

ロードマップに関しては「何それおいしいの?」状態。

こんな状態ではとても管理できません。ツールの使い方を覚えるのと、ツールに合わせて現在の作業内容を変えていく…ってそれは非常にやりづらいですし正直やってられない。

 

2.作成準備

では、何がしたかったのか・何ができれば良いのか、と目的をピックアップしたところ、以下の項目で良いのではないか、という結論に達しました。

バグらしき現象を発見した場合(チケット発行)

  • 概要(タイトル)と詳細を記述(概要のみ必須)
  • 「誰が」「いつ発見したか」のパラメータ
  • ★明らかなバグなのか、仕様かバグか判断が微妙なものなのか、あるいは改善点なのかetc
  • ★バグならば程度(クラッシュなのかエラーなのか警告なのかetc)
  • ★バグならば再現性(毎回なのか不定期なのかetc)
  • ★優先度
  • どのプロジェクトの話か

バグへの対処(チケットのクローズ)

  • 「誰が」「いつ対処したか」のパラメータ
  • ▲対処概要(バグとして対処したのか、仕様なのか、あるいは保留としたのかetc)
  • 対処の詳細

全般・その他

  • プロジェクト管理(あれば良い。タグ付け程度で良い)
  • チケットのキーワード・絞り込み検索
  • (アカウント管理)

 

今回これらの要件を満たすものとして注目したのが、WordPressです。1チケット=1エントリ。チケットへの対処はコメントでやれば最低限のカスタマイズで済むのではないか、という算段です。

Webベースで、DB内のテキスト検索やパラメータによる絞り込みが可能で、しかもパラメータは自分でカスタマイズして追加することができる。

ということでやってみました。

 

初期設定

環境はXAMPP。まずは普通にWordPress(今回は現時点で最新の4.3.1)をインストール。

次に管理画面で設定を行います。

  • 一般設定(サイトの名前とか)
  • 表示設定(抜粋のみの表示にしておく)
  • ディスカッション(今回はコメントが肝なのでここを重点的に)
    • 「新しい記事に対し他のブログからの通知 (ピンバック・トラックバック) を受け付ける 」のチェックは外す
    • 「新しい投稿へのコメントを許可する」のチェックは付けたまま
    • 「名前とメールアドレスの入力を必須にする」はチェックを外す
    • 承認なしで即座にコメントが付くように「コメント表示条件」の項目は両方ともチェックを外す
  • パーマリンク設定(名前でも良いのですがここはシンプルに「/%post_id%/」)

 

プラグイン

  • CKEditor for WordPress(テキストエディタを使いやすくしたいので)
  • Revision Control(自動保存を防ぐため)
  • Search Everything(検索の実装を丸投げする)
  • Types(カスタムフィールドを使用するため)
  • User Submitted Posts(フロントページから投稿できるようにするため)

とりあえずこれらをインストール&設定。

Revision Controlでリビジョン自動保存を無効に。

Search Everythingでタグやカスタムフィールドも検索するように設定して、検索部分のカスタマイズを端折ります(本格的にやるなら自前でWP_Queryに条件追加するので、ここはコストと調整)。

Typesで投稿にカスタムフィールドを追加します。追加するのはすべてラジオボタンタイプで、上記チケット発行の★印、合計4つ。


一例としてこんな感じでカスタムフィールドを設定。

また、今回はいちいちログインして書くのは面倒なのでUser Submitted Postsでフロントページから書き込めるようにします。これは使い方によって要調整かと。

User Submitted PostsはUser NAme, Post Title, Post Tags, Post Category, Post Contentを表示。他は非表示。「Auto Publish?」は「Always Publish immediately」を選択。

 

テーマ

テーマはシンプルなデザインということで公式テーマ「Sparkling colorlib(2.0.1)」をインストールして使用します。

色や背景画像などはお好みで。

ウィジェットにはテキストを追加。下記でfunctions.phpにウィジェット内でショートコードが有効になるフィルターフックを記述していますが、それを利用してテキストウィジェットでUser Submitted Postsの投稿フォームを呼び出します。

 

カスタマイズ

以下、コードのカスタマイズ。

functions.php

コメント部分のカスタマイズ(▲のラジオボタンを追加)がメイン。

/* === コメントのメールアドレス・サイト等を削除 === */
function my_commentForm($fields){
	unset($fields['email']);
	unset($fields['url']);
	unset($fields['comment_notes_before']);
	return $fields;
}
add_filter('comment_form_default_fields' , 'my_commentForm');
/* === コメントのメールアドレス・サイト等の文言を削除・ここまで === */
/* === コメントにラジオボックスを追加 === */
add_filter( 'comment_form_defaults','change_comment_form_input');
function change_comment_form_input($default) {

	//入力項目のカスタマイズ(名前のあとにつづく)

	//フィードバック
	$default['fields']['email'] .= '<p class="comment-form-feedback">
	<label for="correct"><input type="radio" name="feedback" value="修正" id="correct" checked="checked" />'. __('修正') . '</label>
	<label for="specification"><input type="radio" name="feedback" value="仕様" id="specification" />'. __('仕様') . '</label>
	<label for="onhold"><input type="radio" name="feedback" value="保留" id="保留" />'. __('保留') . '</label>
	</p>';

	return $default;
}
//データ更新
add_action( 'comment_post', 'save_comment_meta_data_feedback' );
function save_comment_meta_data_feedback( $comment_id ) {
	$feedback = $_POST['feedback'];
	return update_comment_meta( $comment_id, 'feedback', $feedback, true);
}
//フロントに表示
add_filter( 'get_comment_author_link', 'attach_feedback_to_author' );
function attach_feedback_to_author( $author ) {
	$feedbacks = get_comment_meta( get_comment_ID(), 'feedback');
	if ( $feedbacks ) {
		foreach ($feedbacks as $feedback)
			$author .= " ★" . $feedback;
	}
	return $author; //コメント者の後に続けて出力。
}
/* === コメントにラジオボックスを追加・ここまで === */

/* === テキストウィジェットでショートコードを使用する(User Submitted Posts用) === */
add_filter('widget_text', 'do_shortcode');
/* === テキストウィジェットでショートコードを使用する(User Submitted Posts用)・ここまで === */

style.css

unset($fields['comment_notes_before'])が効かなかったのでcssで消しました。

#email-notes {
	display: none;
}

登録したカスタムフィールドの表示

Sparkling Colorlibのcontent.phpとcontent-single.phpの投稿日や投稿者、編集の後ろに以下のコードを挿入。

<?php
/* key名セット */
$ticketcat = "wpcf-ticketcategory";
$reproducibility = "wpcf-reproducibility";
$importance = "wpcf-importance";
$priority = "wpcf-priority";

/* 値取得 */
$ticketcat_val = get_post_meta(get_the_ID(), $ticketcat, true);
echo "<br />\n";
echo '<span class="posted-on"><i class="fa fa-send-o"></i>種類: ' . esc_html($ticketcat_val) . '</span>';
if($ticketcat_val == "バグ") {
	$reproducibility_val = get_post_meta(get_the_ID(), $reproducibility, true);
	if($reproducibility_val != "") {
		echo '<span class="posted-on"><span class="glyphicon glyphicon-dashboard"></span>再現性: ' . esc_html($reproducibility_val) . '</span>';
	}
	$importance_val = get_post_meta(get_the_ID(), $importance, true);
	if($importance_val != "") {
		echo '<span class="posted-on"><span class="glyphicon glyphicon-warning-sign"></span>重要度: ' . esc_html($importance_val) . '</span>';
	}
}
$priority_val = get_post_meta(get_the_ID(), $priority, true);
echo '<span class="posted-on"><span class="glyphicon glyphicon-exclamation-sign"></span>優先度: ' . esc_html($priority_val) . '</span>';
?>

user-submitted-posts.php

フロントページからカスタムフィールドを登録できるようにカスタマイズ。

/* 99行目(変数宣言)後ろ */
		$ticketcategory = ''; $reproducibility = ''; $importance = ''; $priority = ''; //変数用意
/* 109行目(変数チェック)後ろ */
		if (isset($_POST['ticketcategory']))          $ticketcategory  = sanitize_text_field($_POST['ticketcategory']);
		if (isset($_POST['reproducibility']))         $reproducibility = sanitize_text_field($_POST['reproducibility']);
		if (isset($_POST['importance']))              $importance      = sanitize_text_field($_POST['importance']);
		if (isset($_POST['priority']))                $priority        = sanitize_text_field($_POST['priority']); //自分で用意したカスタムフィールドをチェック
		$result = usp_createPublicSubmission($title, $files, $ip, $author, $url, $email, $tags, $captcha, $verify, $content, $category, $ticketcategory, $reproducibility, $importance, $priority); //追加した変数をusp_createPublicSubmissionに投げるために引数に追加

/* 485行目 */
function usp_createPublicSubmission($title, $files, $ip, $author, $url, $email, $tags, $captcha, $verify, $content, $category, $ticketcategory, $reproducibility, $importance, $priority) { //引数を追加

/* 502, 504行目のタグと本文の必須チェックを外す(任意) */

/* 568行目(update_post_metaでカスタムフィールドに登録している部分)後ろ */
		if (!empty($ticketcategory))  update_post_meta($post_id, "wpcf-ticketcategory",     $ticketcategory);
		if (!empty($reproducibility)) update_post_meta($post_id, "wpcf-reproducibility",    $reproducibility);
		if (!empty($importance))      update_post_meta($post_id, "wpcf-importance",         $importance);
		if (!empty($priority))        update_post_meta($post_id, "wpcf-priority",           $priority); //自分で用意したカスタムフィールドをDBに登録

submission-form.php

フロントページでカスタムフィールドを入力できるようにカスタマイズ。

/* 78行目(カテゴリ選択のセレクトボックス出力直後) */
		<fieldset class="categories">
			<?php _e('種類', 'usp'); ?>
			<label for="bug"><input type="radio" name="ticketcategory" value="バグ" id="bug" checked="checked" <?php echo $required; ?> />バグ</label>
			<label for="demand"><input type="radio" name="ticketcategory" value="要望" id="demand" />要望</label>
			<label for="integrity"><input type="radio" name="ticketcategory" value="整合性" id="integrity" />他の画面との整合性</label>
		</fieldset>
<script>
jQuery(function($) {
	$(window).load(function() { //ウインドウリロード時のラジオボタンのチェック
		outputOption();
	});

	$("input[name=\"ticketcategory\"]:radio").change(function() { //ラジオボタンの値が変更されたらチェック
		outputOption();
	});

/* 再現性・重要度のラジオボタンを表示させる */
	function outputOption() {
		if($("input[name=\"ticketcategory\"]:checked").val() == "バグ") { //「種類」のラジオボタンで「バグ」が選択されていたら、再現性・重要度のラジオボタンを表示させる
//		$("<label></label>").append("<input />").attr("type", "radio").attr("name", "reproducibility").attr("id", "everytime").attr("value", "毎回").appendTo("#reproducibility");
			$("#reproducibility").append('再現性');
			$("#reproducibility").append('<label for="everytime"><input type="radio" name="reproducibility" value="毎回" id="everytime" />毎回</label>');
			$("#reproducibility").append('<label for="sometimes"><input type="radio" name="reproducibility" value="時々" id="sometimes" />時々</label>');
			$("#reproducibility").append('<label for="irregular"><input type="radio" name="reproducibility" value="不定期" id="irregular" />不定期</label>');
			$("#reproducibility").append('<label for="unknown"><input type="radio" name="reproducibility" value="不明" id="unknown" />再現不可・不明</label>');

			$("#importance").append('重要度');
			$("#importance").append('<label for="crush"><input type="radio" name="importance" value="クラッシュ" id="crush" />クラッシュ</label>');
			$("#importance").append('<label for="error"><input type="radio" name="importance" value="エラー" id="error" />エラー</label>');
			$("#importance").append('<label for="warning"><input type="radio" name="importance" value="警告" id="warning" />警告</label>');
			$("#importance").append('<label for="info"><input type="radio" name="importance" value="些細" id="info" />些細</label>');
		}
		else { //「バグ」以外は再現性・重要度のラジオボタンを削除する
			$("#reproducibility").empty();
			$("#importance").empty();
		}
	}
/* 再現性・重要度のラジオボタンを表示させる・ここまで */
});
</script>
		<fieldset class="reproducibility" id="reproducibility">
		</fieldset>
		<fieldset class="importance" id="importance">
		</fieldset>
		<fieldset class="priority">
			<?php _e('優先度', 'usp'); ?>
			<label for="emergency"><input type="radio" name="priority" value="緊急" id="emergency" checked="checked" <?php echo $required; ?> />緊急</label>
			<label for="high"><input type="radio" name="priority" value="高" id="high" />高</label>
			<label for="middle"><input type="radio" name="priority" value="中" id="middle" />中</label>
			<label for="low"><input type="radio" name="priority" value="低" id="low" />低</label>
		</fieldset>

 

これで冒頭に挙げた要件を最低限できるようになった、はず。

プロジェクトはカテゴリで選択式にすればとりあえず絞り込みできるのでそれで(トップページでは全部表示されてしまいますが)。ただ、新しいプロジェクトの追加には管理画面から投稿かカテゴリ追加するだけでなく、User Submitted Postsのオプションで新しく追加したものにチェックを入れないと選択できないので注意。

カスタムフィールドの絞り込みは実装してませんが、Search Everythingにより、キーワード検索にカスタムフィールドの値を突っ込めば該当する項目(+本文にその単語を含むものも混じりますが)が出てくるので当面は簡易的にそれで。

 

完成図

上記のようなカスタマイズをした結果できたのが下図のようなサイト。


トップページ。作ったページに対して2つほどバグとか要望をチケット(のように)発行。

 


新規投稿はサイドバーからできます(今回は特にログイン制限も設けてないので誰でも好きにできてしまいますが)。

 


フィードバックはコメントで。…確かにコメントの文字色が見づらい。コメントフォーム全体に対して背景色を指定すべきですかね…。

まだまだ改善の余地はありますが、最低限の機能の実装としては動くところまでできました。

 

余談

余談ですが、WordPressをBTSとして使うためのプラグインなんていうのも存在するらしいのですが、2010年の記事とだいぶ古いので今でも使えるのか怪しいので今回は使用しませんでした。

タグ: プラグイン, カスタマイズ, カスタムフィールド

 



関連する記事一覧