Redmine

Project ManagementTicket ManagementOpen SourceRuby on RailsIssue TrackingWiki

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

  1. Completely Free Open Source

    • No license fees, usable regardless of team size
    • Rich customization options to adapt to organizational requirements
  2. High Customizability

    • Custom fields, workflows, and UI extensions possible
    • Function expansion through plugins (TestRail integration, mobile app support, etc.)
  3. Simple and Intuitive Operation

    • Low learning cost with basic web interface
    • Immediate start without complex configuration
  4. Rich Integrated Features

    • Centralized management of wiki, forum, time management, and Gantt charts
    • Integration capabilities with numerous external tools

Cons

  1. Outdated User Interface

    • Too basic design and functional constraints
    • Difficult to use for users accustomed to modern UX/UI
  2. Performance Constraints

    • Response speed degradation with large-scale data due to Ruby on Rails characteristics
    • Scalability issues when concurrent access increases
  3. Lack of Advanced Features

    • Limited agile development support capabilities
    • Insufficient real-time collaboration features

Reference Links

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.