hicknhack-software/redmine_hourglass

View on GitHub
lib/hourglass/redmine_time_tracker_import.rb

Summary

Maintainability
A
1 hr
Test Coverage
class Hourglass::RedmineTimeTrackerImport
  class << self
    def start!
      stub_old_plugin unless Redmine::Plugin.installed? :redmine_time_tracker

      TimeTracker.all.each { |time_tracker| create_new_time_tracker time_tracker }
      TimeBooking.all.each { |time_booking| create_new_time_booking time_booking }

      TimeLog.all.each do |time_log|
        if time_log.time_bookings.empty?
          create_new_time_log time_log
        elsif time_log.bookable_hours > 0
          find_gaps(time_log.time_bookings, time_log.started_on, time_log.stopped_at).each do |gap|
            create_new_time_log time_log, *gap
          end
        end
      end
    end

    private
    def create_new_time_tracker(time_tracker)
      new_time_tracker = Hourglass::TimeTracker.find_or_initialize_by(
          user_id: time_tracker.user_id
      )
      new_time_tracker.attributes = {
          start: time_tracker.started_on,
          comments: time_tracker.comments,
          round: time_tracker.round,
          project_id: time_tracker.project_id,
          issue_id: time_tracker.issue_id,
          activity_id: time_tracker.activity_id
      }
      log_errors time_tracker, new_time_tracker unless new_time_tracker.save
    end

    def create_new_time_log(time_log, start = time_log.started_on, stop = time_log.stopped_at)
      new_time_log = Hourglass::TimeLog.find_or_initialize_by(
          start: start,
          stop: stop,
          user_id: time_log.user_id
      )
      new_time_log.attributes = {
          comments: time_log.comments
      }
      log_errors time_log, new_time_log unless new_time_log.save
    end

    def create_new_time_booking(time_booking)
      new_time_booking = Hourglass::TimeBooking.find_or_initialize_by(
          time_entry_id: time_booking.time_entry_id
      )
      time_log_attributes = {
          start: time_booking.started_on,
          stop: time_booking.stopped_at,
          comments: time_booking.time_log.comments,
          user_id: time_booking.time_log.user_id
      }
      time_log_attributes[:id] = new_time_booking.time_log_id if new_time_booking.persisted?
      new_time_booking.attributes = {
          start: time_booking.started_on,
          stop: time_booking.stopped_at,
          time_log_attributes: time_log_attributes
      }
      log_errors time_booking, new_time_booking unless new_time_booking.save
    end

    def log_errors(old, new)
      puts "There was a problem while importing #{old.class.name} #{old.id}:"
      puts "Attributes: #{old.attributes.to_json}"
      puts "Errors: #{new.errors.full_messages.to_sentence}."
      if new.errors.count == 1 && (new.respond_to?(:time_log) ? new.time_log.errors.added?(:base, :overlaps) : new.errors.added?(:base, :overlaps))
        new.save validate: false
        puts 'It was added nevertheless, delete it yourself if you want it removed.'
        puts "New record: #{new.attributes.to_json}"
      end
      puts '-------------------'
    end

    def find_gaps(bookings, start, stop)
      sorted = bookings.sort_by { |booking| booking.start }
      gaps = []

      unless sorted.first.start == start
        gaps << [start, sorted.first.start]
      end

      sorted.each_with_index do |booking, i|
        unless sorted[i + 1].nil?
          unless booking.stop == sorted[i + 1].start
            gaps << [booking.stop, sorted[i + 1].start]
          end
        end
      end

      unless sorted.last.stop == stop
        gaps << [sorted.last.stop, stop]
      end

      gaps
    end

    def stub_old_plugin
      Object.const_set :TimeTracker, Class.new(ActiveRecord::Base) unless const_defined?(:TimeTracker)
      stub_old_time_log unless const_defined?(:TimeLog)
      stub_old_time_booking unless const_defined?(:TimeBooking)
    end

    def stub_old_time_log
      time_log = Class.new ActiveRecord::Base do
        has_many :time_bookings

        def hours_spent
          ((started_on.to_i - stopped_at.to_i) / 3600.0).to_f
        end

        def bookable_hours
          hours_spent - hours_booked
        end

        def hours_booked
          time_booked = 0
          time_bookings.each do |tb|
            time_booked += tb.hours_spent
          end
          time_booked
        end
      end
      Object.const_set :TimeLog, time_log
    end

    def stub_old_time_booking
      time_booking = Class.new ActiveRecord::Base do
        belongs_to :time_log

        def hours_spent
          ((started_on.to_i - stopped_at.to_i) / 3600.0).to_f
        end
      end
      Object.const_set :TimeBooking, time_booking
    end
  end
end