Redmine
Ticket Management Tool
Redmine
Overview
Redmine is an open-source project management and issue tracking tool developed with Ruby on Rails. Provided free under the GPL v2 license, it offers integrated functionality for managing multiple projects, task tracking, time management, wiki, and forum features through an intuitive web-based interface. With high customizability and a rich plugin ecosystem, it's utilized across various industries and organization sizes.
Details
Redmine has over 10 years of proven track record as an open-source project management tool, characterized by its simple yet powerful feature design.
Key Features
- Flexible Issue Management: Standard Bug, Feature, Support types with ability to create custom types
- Project Hierarchy Management: Supports complex project structures with parent-child relationships
- Integrated Wiki Functionality: Project-specific wikis for documentation management and knowledge sharing
- Time Tracking: Detailed effort management and report generation capabilities
- Role-Based Access Control: Flexible access management through detailed permission settings
Technical Specifications
- Architecture: Ruby on Rails framework, supports MySQL/PostgreSQL/SQLite
- Extensibility: Rich plugin ecosystem, REST API provided
- Deployment: Supports both on-premises and cloud deployment, Docker compatible
- Internationalization: Multi-language support (including Japanese)
Pros and Cons
Pros
-
Completely Free Open Source
- No license fees, usable regardless of team size
- Rich customization options to adapt to organizational requirements
-
High Customizability
- Custom fields, workflows, and UI extensions possible
- Function expansion through plugins (TestRail integration, mobile app support, etc.)
-
Simple and Intuitive Operation
- Low learning cost with basic web interface
- Immediate start without complex configuration
-
Rich Integrated Features
- Centralized management of wiki, forum, time management, and Gantt charts
- Integration capabilities with numerous external tools
Cons
-
Outdated User Interface
- Too basic design and functional constraints
- Difficult to use for users accustomed to modern UX/UI
-
Performance Constraints
- Response speed degradation with large-scale data due to Ruby on Rails characteristics
- Scalability issues when concurrent access increases
-
Lack of Advanced Features
- Limited agile development support capabilities
- Insufficient real-time collaboration features
Reference Links
- Redmine Official Website
- Redmine User Guide
- Redmine REST API Documentation
- Redmine Plugin Directory
- Redmine Community Forum
Basic Usage Examples
1. Project Setup and Initial Configuration
# Project creation using Redmine REST API (Ruby example)
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)
# Project-specific settings
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. Issue Management and Workflows
// Issue management using Redmine REST API (JavaScript example)
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();
// Handle attachments
if (issueData.attachments) {
await this.uploadAttachments(createdIssue.issue.id, issueData.attachments);
}
// Create relations
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. Time Management and Reporting
# Redmine time tracking and report generation (Python example)
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):
"""Log time entry"""
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):
"""Project time report"""
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):
"""Time data analysis"""
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):
"""Generate burndown chart data"""
# Get version information
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']
# Get issue list
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']
# Calculate burndown data
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 Management and Knowledge Base
# Redmine Wiki management (Ruby example)
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)
# Create basic documentation structure
documentation_structure = [
{
title: 'ProjectOverview',
content: generate_project_overview_content,
children: [
{ title: 'Requirements', content: '# Requirements\n\nProject requirements documentation.' },
{ title: 'Architecture', content: '# System Architecture\n\nArchitecture diagrams and explanations.' }
]
},
{
title: 'DevelopmentGuide',
content: generate_development_guide_content,
children: [
{ title: 'SetupInstructions', content: '# Development Environment Setup\n\nEnvironment setup procedures.' },
{ title: 'CodingStandards', content: '# Coding Standards\n\nCoding rules and guidelines.' },
{ title: 'TestingGuidelines', content: '# Testing Guidelines\n\nTesting policies and procedures.' }
]
},
{
title: 'UserManual',
content: generate_user_manual_content,
children: [
{ title: 'GettingStarted', content: '# Getting Started\n\nHow to start using the system.' },
{ title: 'FeatureGuide', content: '# Feature Guide\n\nHow to use each feature.' }
]
}
]
# Create documentation
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
# Project Overview
## Purpose
Purpose and background of this project.
## Scope
Project scope and constraints.
## Stakeholders
| Role | Person | Contact |
|------|--------|---------|
| Project Manager | | |
| Development Lead | | |
| Quality Assurance Lead | | |
## Schedule
Key milestones and deadlines.
CONTENT
end
def generate_development_guide_content
<<~CONTENT
# Development Guide
## Development Process
Explanation of the development process adopted in this project.
## Branch Strategy
Git branch operation rules.
## Review Process
Code review and pull request guidelines.
## Deployment
Deployment procedures for each environment.
CONTENT
end
def generate_user_manual_content
<<~CONTENT
# User Manual
## System Overview
Overall system view and main features.
## Login Method
System access procedures.
## Feature-specific Operations
Detailed operation methods for each feature.
## Troubleshooting
Common problems and solutions.
CONTENT
end
end
5. Plugin Utilization and Customization
# Redmine plugin development and customization examples
class RedmineCustomizations
def self.install_useful_plugins
plugins = [
{
name: 'redmine_agile',
description: 'Agile development support (Scrum boards, burndown charts)',
installation: 'gem install redmine_agile'
},
{
name: 'redmine_dmsf',
description: 'Document management system',
installation: 'git clone https://github.com/danmunn/redmine_dmsf.git'
},
{
name: 'redmine_contacts',
description: 'Customer and contact management',
installation: 'gem install redmine_contacts'
},
{
name: 'redmine_spent_time',
description: 'Advanced time management and reporting',
installation: 'git clone https://github.com/eyestreet/redmine_spent_time.git'
}
]
plugins
end
def self.create_custom_field(field_config)
# Custom field creation example
{
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)
# Email notification setup
notification_rules.each do |rule|
# On issue creation
setup_notification_rule(project_id, 'issue_add', rule[:on_create])
# On issue update
setup_notification_rule(project_id, 'issue_edit', rule[:on_update])
# On comment addition
setup_notification_rule(project_id, 'issue_note_added', rule[:on_comment])
end
end
def self.integrate_with_external_tools(tool_configs)
integrations = {}
# Git integration setup
if tool_configs[:git]
integrations[:git] = setup_git_integration(tool_configs[:git])
end
# CI/CD integration setup
if tool_configs[:ci_cd]
integrations[:ci_cd] = setup_ci_cd_integration(tool_configs[:ci_cd])
end
# Slack integration setup
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)
# Integration with Jenkins, GitHub Actions, etc.
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. Backup and Maintenance
#!/bin/bash
# Redmine backup and maintenance script
# Database backup
backup_database() {
BACKUP_DIR="/backup/redmine/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# For MySQL
mysqldump -u redmine_user -p redmine_production > $BACKUP_DIR/redmine_db_$(date +%Y%m%d_%H%M%S).sql
# For PostgreSQL
# pg_dump -U redmine_user redmine_production > $BACKUP_DIR/redmine_db_$(date +%Y%m%d_%H%M%S).sql
}
# File backup
backup_files() {
BACKUP_DIR="/backup/redmine/$(date +%Y%m%d)"
REDMINE_ROOT="/var/www/redmine"
# Configuration files
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
# Attachment files
tar -czf $BACKUP_DIR/redmine_files_$(date +%Y%m%d_%H%M%S).tar.gz \
$REDMINE_ROOT/files
# Plugins
tar -czf $BACKUP_DIR/redmine_plugins_$(date +%Y%m%d_%H%M%S).tar.gz \
$REDMINE_ROOT/plugins
}
# System maintenance
system_maintenance() {
cd /var/www/redmine
# Database cleanup
bundle exec rake redmine:cleanup RAILS_ENV=production
# Session cleanup
bundle exec rake redmine:sessions:clear RAILS_ENV=production
# Log rotation
find log/ -name "*.log" -type f -mtime +30 -delete
# Temporary file cleanup
bundle exec rake tmp:clear RAILS_ENV=production
}
# Performance optimization
optimize_performance() {
cd /var/www/redmine
# Database optimization
bundle exec rake db:optimize RAILS_ENV=production
# Cache clear
bundle exec rake tmp:cache:clear RAILS_ENV=production
# Asset regeneration
bundle exec rake assets:precompile RAILS_ENV=production
}
# Main execution
main() {
echo "Starting Redmine maintenance..."
backup_database
backup_files
system_maintenance
optimize_performance
echo "Maintenance completed successfully."
}
main "$@"
Redmine is a powerful open-source project management tool trusted and utilized by many organizations. With cost efficiency and high customizability, it enables the construction of flexible project management environments tailored to specific organizational needs.