robertgauld/OSMExtender

View on GitHub
app/reports/badge_completion_matrix_report.rb

Summary

Maintainability
D
2 days
Test Coverage
class BadgeCompletionMatrixReport < LongRunningReport
  class << self
    private
    def cache_key(user_id, section_id, include_core:, include_challenge:, include_staged:, include_activity:, exclude_not_started:, exclude_all_finished:)
      "#{self.name}-a-#{user_id}-#{section_id}-"
      + [include_core, include_challenge, include_staged, include_activity, exclude_not_started, exclude_all_finished]
        .map { |v| v ? 't' : 'f' }.join
    end

    def fetch_data(user_id, section_id, include_core:, include_challenge:, include_staged:, include_activity:, exclude_not_started:, exclude_all_finished:)
      user = User.find(user_id)
      osm_api = user.osm_api
      section = Osm::Section.get(osm_api, section_id)

      matrix = []
      names = []
      member_ids = []

      badges = []
      badges += Osm::CoreBadge.get_badges_for_section(user.osm_api, section) if include_core
      badges += Osm::StagedBadge.get_badges_for_section(user.osm_api, section) if include_staged
      badges += Osm::ChallengeBadge.get_badges_for_section(user.osm_api, section) if include_challenge
      badges += Osm::ActivityBadge.get_badges_for_section(user.osm_api, section) if include_activity
      badges.select!{ |b| !b.add_columns? } # Skip badges we add columns to

      unless badges.first.nil?
        data = badges.first.get_data_for_section(user.osm_api, section)
        names = data.map{ |i| "#{i.first_name} #{i.last_name}" }
        member_ids = data.map{ |i| i.member_id }
      end

      # Exclude any badges matching the criteria
      if exclude_not_started || exclude_all_finished
        summary = Osm::Badge.get_summary_for_section(user.osm_api, section)
        started = Hash.new(0)  # Count of people who have started each badge
        finished = Hash.new(0) # Count of people who have finished each badge
        summary.each do |member|
          member.keys.select{ |k| !!k.match(/\d+_\d+/)}.each do |key| # Keys which relate to badge information
            started[key] += 1 if member[key].eql?(:started)
            finished[key] += 1 if [:due, :awarded].include?(member[key])
          end
        end # each member in summary

        badges.select! do |badge|
          exclude = false
          if exclude_not_started
            # exclude the badge if noone has started it
            exclude ||= started[badge.identifier].eql?(0)
          end
          if exclude_all_finished
            # exclude the badge if everyone has finished it
            exclude ||= finished[badge.identifier].eql?(summary.count)
          end
          !exclude
        end
      end

      # Get badge data
      badges.each do |badge|
        completion_data = badge.get_data_for_section(user.osm_api, section)
        completion_data.sort!{ |a,b| member_ids.find_index(a.member_id) <=> member_ids.find_index(b.member_id) }
        badge.requirements.each do |requirement|
          met_data = completion_data.map do |i|
            met = nil

            # Workout if badge is completed or awarded
            if badge.has_levels? # Staged
              met = :awarded if requirement.mod.letter < ('a'..'z').to_a[i.awarded]
              met ||= :completed if requirement.mod.letter.eql?(('a'..'z').to_a[i.earnt - 1])
            else # 'Normal'
              met = :awarded if i.awarded?
              met ||= :completed if i.earnt?
            end

            # Workout if the requirmeent is needed to complete the badge (if started)
            if met.nil? && i.started?
              unless badge.has_levels? && !requirement.mod.letter.eql?(('a'..'z').to_a[i.started - 1])
                if i.requirement_met?(requirement.id)
                  met = :yes
                else
                  # Requirement not met but is it actually needed?
                  needed_for_total = (i.total_gained < badge.min_requirements_required)
                  modules_gained = i.modules_gained
                  needed_for_module_total = (modules_gained.size < badge.min_modules_required)
                  needed_for_module = !modules_gained.include?(requirement.mod.letter)
                  modules_needed = i.badge.requires_modules.nil? ? [] : i.badge.requires_modules.select{ |a| !a.map{ |b| modules_gained.include?(b) }.include?(true) }.flatten
                  if needed_for_total || (needed_for_module && needed_for_module_total) || (needed_for_module && modules_needed.include?(requirement.mod.letter))
                    met = :no
                  else
                    met = :not_needed
                  end
                end
              end
            end #started?
            met || :not_started
          end

          matrix.push ([
            badge.type,
            badge.name,
            (badge.has_levels? ? ('a'..'z').to_a.index(requirement.mod.letter)+1 : requirement.mod.letter),
            requirement.name,
            *met_data,
          ])
        end # each badge.requirement
      end # each badge

      {
        names: names,
        matrix: matrix,
      }
    end
  end
end