Redmine
チケット管理ツール
Redmine
概要
Redmineは、Ruby on Railsで開発されたオープンソースのプロジェクト管理・イシュー追跡ツールです。GPL v2ライセンスの下で無料提供され、Web ベースの直感的なインターフェースを通じて、複数プロジェクトの管理、タスク追跡、時間管理、Wiki、フォーラム機能を統合的に提供します。高いカスタマイズ性と豊富なプラグインエコシステムにより、様々な業界・規模の組織で活用されています。
詳細
Redmineは、オープンソースプロジェクト管理ツールとして10年以上の実績を持ち、シンプルながら強力な機能を提供する設計が特徴です。
主要な特徴
- 柔軟なイシュー管理: Bug、Feature、Supportの標準タイプに加え、カスタムタイプ作成可能
- プロジェクト階層管理: 親子関係のある複雑なプロジェクト構造をサポート
- 統合Wiki機能: プロジェクト毎のWikiによる文書管理とナレッジ共有
- 時間追跡: 詳細な工数管理とレポート生成機能
- ロールベースアクセス制御: 細かな権限設定による柔軟なアクセス管理
技術仕様
- アーキテクチャ: Ruby on Rails フレームワーク、MySQL/PostgreSQL/SQLite対応
- 拡張性: 豊富なプラグインエコシステム、REST API提供
- デプロイメント: オンプレミス・クラウド両対応、Docker対応
- 国際化: 多言語対応(日本語含む)
メリット・デメリット
メリット
-
完全無料のオープンソース
- ライセンス費用不要、チーム規模に関係なく利用可能
- 豊富なカスタマイズオプションによる組織要件への適応
-
高いカスタマイズ性
- カスタムフィールド、ワークフロー、UI拡張が可能
- プラグインによる機能拡張(TestRail統合、モバイルアプリ対応等)
-
シンプルで直感的な操作性
- 基本的なWebインターフェースで学習コストが低い
- 複雑な設定なしで即座に利用開始可能
-
統合機能の充実
- Wiki、フォーラム、時間管理、ガントチャートを一元管理
- 多数の外部ツールとの連携機能
デメリット
-
ユーザーインターフェースの古さ
- 基本的すぎるデザインと機能性の制約
- モダンなUX/UIに慣れたユーザーには使いにくさ
-
パフォーマンスの制約
- Ruby on Railsの特性上、大規模データでの応答速度低下
- 同時アクセス数増加時のスケーラビリティ課題
-
高度な機能の不足
- アジャイル開発支援機能の限界
- リアルタイムコラボレーション機能の不備
参考ページ
基本的な使い方
1. プロジェクト設定と初期構成
# Redmine REST API を使用したプロジェクト作成(Ruby例)
require 'net/http'
require 'json'
class RedmineAPI
def initialize(redmine_url, api_key)
@base_url = redmine_url
@api_key = api_key
end
def create_project(project_data)
uri = URI("#{@base_url}/projects.json")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
request = Net::HTTP::Post.new(uri)
request['X-Redmine-API-Key'] = @api_key
request['Content-Type'] = 'application/json'
project_params = {
project: {
name: project_data[:name],
identifier: project_data[:identifier],
description: project_data[:description],
homepage: project_data[:homepage],
is_public: project_data[:is_public] || false,
parent_id: project_data[:parent_id],
inherit_members: project_data[:inherit_members] || false,
enabled_modules: project_data[:modules] || ['issue_tracking', 'time_tracking', 'wiki', 'repository'],
trackers: project_data[:trackers] || [1, 2, 3], # Bug, Feature, Support
custom_fields: project_data[:custom_fields] || []
}
}
request.body = project_params.to_json
response = http.request(request)
if response.code == '201'
project = JSON.parse(response.body)
setup_project_configuration(project['project']['id'], project_data)
project
else
raise "Project creation failed: #{response.body}"
end
end
private
def setup_project_configuration(project_id, config)
# プロジェクト固有の設定
setup_project_members(project_id, config[:members]) if config[:members]
setup_issue_categories(project_id, config[:categories]) if config[:categories]
setup_versions(project_id, config[:versions]) if config[:versions]
create_wiki_structure(project_id, config[:wiki_pages]) if config[:wiki_pages]
end
end
2. イシュー管理とワークフロー
// Redmine REST API を使用したイシュー管理(JavaScript例)
class RedmineIssueManager {
constructor(redmineUrl, apiKey) {
this.baseUrl = redmineUrl;
this.apiKey = apiKey;
}
async createIssue(projectId, issueData) {
const issue = {
project_id: projectId,
tracker_id: issueData.tracker_id || 1, // Bug=1, Feature=2, Support=3
status_id: issueData.status_id || 1, // New
priority_id: issueData.priority_id || 2, // Normal
subject: issueData.subject,
description: issueData.description,
category_id: issueData.category_id,
fixed_version_id: issueData.fixed_version_id,
assigned_to_id: issueData.assigned_to_id,
parent_issue_id: issueData.parent_issue_id,
start_date: issueData.start_date,
due_date: issueData.due_date,
estimated_hours: issueData.estimated_hours,
done_ratio: issueData.done_ratio || 0,
custom_fields: issueData.custom_fields || [],
watcher_user_ids: issueData.watchers || []
};
const response = await fetch(`${this.baseUrl}/issues.json`, {
method: 'POST',
headers: {
'X-Redmine-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ issue })
});
if (!response.ok) {
throw new Error(`Issue creation failed: ${response.statusText}`);
}
const createdIssue = await response.json();
// 添付ファイル処理
if (issueData.attachments) {
await this.uploadAttachments(createdIssue.issue.id, issueData.attachments);
}
// 関連づけ設定
if (issueData.relations) {
await this.createIssueRelations(createdIssue.issue.id, issueData.relations);
}
return createdIssue;
}
async updateIssueStatus(issueId, statusId, notes = '') {
const issue = {
status_id: statusId,
notes: notes
};
const response = await fetch(`${this.baseUrl}/issues/${issueId}.json`, {
method: 'PUT',
headers: {
'X-Redmine-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ issue })
});
return response.ok;
}
async createIssueRelations(issueId, relations) {
for (const relation of relations) {
await fetch(`${this.baseUrl}/issues/${issueId}/relations.json`, {
method: 'POST',
headers: {
'X-Redmine-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
relation: {
issue_to_id: relation.issue_to_id,
relation_type: relation.type, // relates, duplicates, blocks, precedes
delay: relation.delay || null
}
})
});
}
}
}
3. 時間管理とレポート機能
# Redmine 時間追跡とレポート生成(Python例)
import requests
import json
from datetime import datetime, timedelta
import pandas as pd
class RedmineTimeTracking:
def __init__(self, redmine_url, api_key):
self.base_url = redmine_url
self.api_key = api_key
self.headers = {
'X-Redmine-API-Key': api_key,
'Content-Type': 'application/json'
}
def log_time_entry(self, issue_id, hours, activity_id, comments='', spent_on=None):
"""工数記録"""
time_entry = {
'issue_id': issue_id,
'hours': hours,
'activity_id': activity_id, # Development=9, Testing=10, Documentation=11
'comments': comments,
'spent_on': spent_on or datetime.now().strftime('%Y-%m-%d')
}
response = requests.post(
f'{self.base_url}/time_entries.json',
headers=self.headers,
data=json.dumps({'time_entry': time_entry})
)
return response.status_code == 201
def get_project_time_report(self, project_id, from_date, to_date):
"""プロジェクト工数レポート"""
params = {
'project_id': project_id,
'from': from_date,
'to': to_date,
'limit': 100
}
response = requests.get(
f'{self.base_url}/time_entries.json',
headers=self.headers,
params=params
)
if response.status_code == 200:
time_entries = response.json()['time_entries']
return self.analyze_time_data(time_entries)
return None
def analyze_time_data(self, time_entries):
"""工数データ分析"""
df = pd.DataFrame(time_entries)
analysis = {
'total_hours': df['hours'].sum(),
'entries_count': len(df),
'by_user': df.groupby('user')['hours'].sum().to_dict(),
'by_activity': df.groupby('activity')['hours'].sum().to_dict(),
'by_issue': df.groupby('issue')['hours'].sum().to_dict(),
'daily_breakdown': df.groupby('spent_on')['hours'].sum().to_dict()
}
return analysis
def generate_burndown_data(self, project_id, version_id):
"""バーンダウンチャートデータ生成"""
# バージョン情報取得
version_response = requests.get(
f'{self.base_url}/versions/{version_id}.json',
headers=self.headers
)
if version_response.status_code != 200:
return None
version = version_response.json()['version']
# イシュー一覧取得
issues_response = requests.get(
f'{self.base_url}/issues.json',
headers=self.headers,
params={
'project_id': project_id,
'fixed_version_id': version_id,
'limit': 100
}
)
if issues_response.status_code != 200:
return None
issues = issues_response.json()['issues']
# バーンダウンデータ計算
total_estimated = sum(issue.get('estimated_hours', 0) for issue in issues)
completed_hours = sum(
issue.get('estimated_hours', 0)
for issue in issues
if issue['status']['id'] in [5, 6] # Closed, Rejected
)
remaining_hours = total_estimated - completed_hours
return {
'version_name': version['name'],
'due_date': version.get('due_date'),
'total_estimated': total_estimated,
'completed_hours': completed_hours,
'remaining_hours': remaining_hours,
'progress_percentage': (completed_hours / total_estimated * 100) if total_estimated > 0 else 0
}
4. Wiki管理とナレッジベース
# Redmine Wiki管理(Ruby例)
class RedmineWikiManager
def initialize(redmine_url, api_key)
@base_url = redmine_url
@api_key = api_key
end
def create_wiki_page(project_id, page_title, content, parent_title = nil)
uri = URI("#{@base_url}/projects/#{project_id}/wiki/#{page_title}.json")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
request = Net::HTTP::Put.new(uri)
request['X-Redmine-API-Key'] = @api_key
request['Content-Type'] = 'application/json'
wiki_page = {
text: content,
comments: "Page created via API",
parent_title: parent_title
}
request.body = { wiki_page: wiki_page }.to_json
response = http.request(request)
response.code == '200'
end
def setup_project_documentation(project_id)
# 基本ドキュメント構造作成
documentation_structure = [
{
title: 'ProjectOverview',
content: generate_project_overview_content,
children: [
{ title: 'Requirements', content: '# 要件定義\n\nプロジェクトの要件を記載します。' },
{ title: 'Architecture', content: '# システム構成\n\nアーキテクチャ図と説明。' }
]
},
{
title: 'DevelopmentGuide',
content: generate_development_guide_content,
children: [
{ title: 'SetupInstructions', content: '# 開発環境構築\n\n環境構築手順。' },
{ title: 'CodingStandards', content: '# コーディング規約\n\nコーディングルール。' },
{ title: 'TestingGuidelines', content: '# テストガイドライン\n\nテスト方針と手順。' }
]
},
{
title: 'UserManual',
content: generate_user_manual_content,
children: [
{ title: 'GettingStarted', content: '# はじめに\n\n利用開始方法。' },
{ title: 'FeatureGuide', content: '# 機能説明\n\n各機能の使い方。' }
]
}
]
# ドキュメント作成
documentation_structure.each do |section|
create_wiki_page(project_id, section[:title], section[:content])
section[:children]&.each do |child|
create_wiki_page(project_id, child[:title], child[:content], section[:title])
end
end
end
private
def generate_project_overview_content
<<~CONTENT
# プロジェクト概要
## 目的
このプロジェクトの目的と背景を記載します。
## スコープ
プロジェクトの範囲と制約事項。
## ステークホルダー
| 役割 | 担当者 | 連絡先 |
|------|--------|--------|
| プロジェクトマネージャー | | |
| 開発リーダー | | |
| 品質管理責任者 | | |
## スケジュール
主要マイルストーンと期日。
CONTENT
end
def generate_development_guide_content
<<~CONTENT
# 開発ガイド
## 開発プロセス
本プロジェクトで採用する開発プロセスの説明。
## ブランチ戦略
Git ブランチ運用ルール。
## レビュープロセス
コードレビューとプルリクエストのガイドライン。
## デプロイメント
各環境へのデプロイ手順。
CONTENT
end
def generate_user_manual_content
<<~CONTENT
# ユーザーマニュアル
## システム概要
システムの全体像と主要機能。
## ログイン方法
システムアクセス手順。
## 機能別操作説明
各機能の詳細な操作方法。
## トラブルシューティング
よくある問題と解決方法。
CONTENT
end
end
5. プラグイン活用とカスタマイズ
# Redmine プラグイン開発とカスタマイズ例
class RedmineCustomizations
def self.install_useful_plugins
plugins = [
{
name: 'redmine_agile',
description: 'アジャイル開発支援(Scrumボード、バーンダウンチャート)',
installation: 'gem install redmine_agile'
},
{
name: 'redmine_dmsf',
description: 'ドキュメント管理システム',
installation: 'git clone https://github.com/danmunn/redmine_dmsf.git'
},
{
name: 'redmine_contacts',
description: '顧客・連絡先管理',
installation: 'gem install redmine_contacts'
},
{
name: 'redmine_spent_time',
description: '高度な時間管理とレポート',
installation: 'git clone https://github.com/eyestreet/redmine_spent_time.git'
}
]
plugins
end
def self.create_custom_field(field_config)
# カスタムフィールド作成例
{
name: field_config[:name],
field_format: field_config[:type], # string, text, int, float, date, bool, list
possible_values: field_config[:options],
is_required: field_config[:required] || false,
is_for_all: field_config[:global] || false,
trackers: field_config[:trackers] || [1, 2, 3], # Bug, Feature, Support
projects: field_config[:projects] || []
}
end
def self.setup_email_notifications(project_id, notification_rules)
# メール通知設定
notification_rules.each do |rule|
# イシュー作成時
setup_notification_rule(project_id, 'issue_add', rule[:on_create])
# イシュー更新時
setup_notification_rule(project_id, 'issue_edit', rule[:on_update])
# コメント追加時
setup_notification_rule(project_id, 'issue_note_added', rule[:on_comment])
end
end
def self.integrate_with_external_tools(tool_configs)
integrations = {}
# Git連携設定
if tool_configs[:git]
integrations[:git] = setup_git_integration(tool_configs[:git])
end
# CI/CD連携設定
if tool_configs[:ci_cd]
integrations[:ci_cd] = setup_ci_cd_integration(tool_configs[:ci_cd])
end
# Slack連携設定
if tool_configs[:slack]
integrations[:slack] = setup_slack_integration(tool_configs[:slack])
end
integrations
end
private
def self.setup_git_integration(git_config)
{
repository_type: git_config[:type], # Git, SVN, Mercurial
url: git_config[:url],
login: git_config[:username],
password: git_config[:password_or_token],
path_encoding: 'UTF-8',
log_encoding: 'UTF-8',
extra_info: git_config[:extra_options] || {}
}
end
def self.setup_ci_cd_integration(ci_config)
# Jenkins, GitHub Actions等との連携設定
webhook_config = {
url: ci_config[:webhook_url],
events: ['issue_updated', 'issue_closed'],
secret_token: ci_config[:secret],
verify_ssl: ci_config[:verify_ssl] || true
}
webhook_config
end
def self.setup_slack_integration(slack_config)
{
webhook_url: slack_config[:webhook_url],
channel: slack_config[:channel],
username: 'Redmine Bot',
icon_emoji: ':redmine:',
events: slack_config[:events] || ['issue_add', 'issue_edit', 'issue_closed']
}
end
end
6. バックアップとメンテナンス
#!/bin/bash
# Redmine バックアップとメンテナンススクリプト
# データベースバックアップ
backup_database() {
BACKUP_DIR="/backup/redmine/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# MySQLの場合
mysqldump -u redmine_user -p redmine_production > $BACKUP_DIR/redmine_db_$(date +%Y%m%d_%H%M%S).sql
# PostgreSQLの場合
# pg_dump -U redmine_user redmine_production > $BACKUP_DIR/redmine_db_$(date +%Y%m%d_%H%M%S).sql
}
# ファイルバックアップ
backup_files() {
BACKUP_DIR="/backup/redmine/$(date +%Y%m%d)"
REDMINE_ROOT="/var/www/redmine"
# 設定ファイル
tar -czf $BACKUP_DIR/redmine_config_$(date +%Y%m%d_%H%M%S).tar.gz \
$REDMINE_ROOT/config/database.yml \
$REDMINE_ROOT/config/configuration.yml \
$REDMINE_ROOT/config/additional_environment.rb
# 添付ファイル
tar -czf $BACKUP_DIR/redmine_files_$(date +%Y%m%d_%H%M%S).tar.gz \
$REDMINE_ROOT/files
# プラグイン
tar -czf $BACKUP_DIR/redmine_plugins_$(date +%Y%m%d_%H%M%S).tar.gz \
$REDMINE_ROOT/plugins
}
# システムメンテナンス
system_maintenance() {
cd /var/www/redmine
# データベースクリーンアップ
bundle exec rake redmine:cleanup RAILS_ENV=production
# セッションクリーンアップ
bundle exec rake redmine:sessions:clear RAILS_ENV=production
# ログローテーション
find log/ -name "*.log" -type f -mtime +30 -delete
# 一時ファイルクリーンアップ
bundle exec rake tmp:clear RAILS_ENV=production
}
# パフォーマンス最適化
optimize_performance() {
cd /var/www/redmine
# データベース最適化
bundle exec rake db:optimize RAILS_ENV=production
# キャッシュクリア
bundle exec rake tmp:cache:clear RAILS_ENV=production
# アセット再生成
bundle exec rake assets:precompile RAILS_ENV=production
}
# メイン実行
main() {
echo "Starting Redmine maintenance..."
backup_database
backup_files
system_maintenance
optimize_performance
echo "Maintenance completed successfully."
}
main "$@"
Redmineは、オープンソースの強力なプロジェクト管理ツールとして、多くの組織で信頼され活用されています。コスト効率と高いカスタマイズ性により、組織の特定ニーズに合わせた柔軟なプロジェクト管理環境を構築できます。