Coursemology/coursemology2

View on GitHub
app/channels/course/monitoring/live_monitoring_channel.rb

Summary

Maintainability
A
25 mins
Test Coverage
# frozen_string_literal: true
class Course::Monitoring::LiveMonitoringChannel < Course::Channel
  include Course::UsersHelper

  DEFAULT_VIEW_HEARTBEATS_LIMIT = 10
  ACTIONS = { pulse: :pulse, terminate: :terminate, viewed: :viewed, watch: :watch }.freeze

  def subscribed
    monitor_id = params[:monitor_id]
    @monitor = Course::Monitoring::Monitor.find(monitor_id)
    reject unless @monitor.present? && can?(:read, @monitor)

    stream_for @monitor
  end

  class << self
    def broadcast_pulse_to(monitor, session, snapshot)
      broadcast_from monitor, :pulse, { userId: session.creator_id, snapshot: snapshot }
    end

    def broadcast_terminate(monitor, session)
      broadcast_from monitor, :terminate, session.creator_id
    end

    def broadcast_from(monitor, action, payload)
      broadcast_to monitor, { action: ACTIONS[action], payload: payload }.compact
    end
  end

  def watch
    active_snapshots = active_sessions_snapshots
    students = current_course.students.order(:phantom, :name)

    snapshots = students.to_h do |student|
      user_id = student.user_id
      [user_id, active_snapshots[user_id] || { userName: student.name }]
    end

    broadcast_watch students.map(&:user_id), snapshots, groups
  end

  def view(data)
    session_id, limit = data['session_id'], data['limit'] || DEFAULT_VIEW_HEARTBEATS_LIMIT
    return unless (session = @monitor.sessions.find(session_id))

    recent_heartbeats = (limit == -1 ? session.heartbeats : session.heartbeats.last(limit)).map do |heartbeat|
      {
        stale: heartbeat.stale,
        userAgent: heartbeat.user_agent,
        ipAddress: heartbeat.ip_address,
        generatedAt: heartbeat.generated_at,
        isValid: @monitor.valid_secret?(heartbeat.user_agent)
      }.compact
    end

    broadcast_viewed recent_heartbeats
  end

  private

  def active_sessions_snapshots
    @monitor.sessions.includes(:heartbeats, :creator).to_h do |session|
      last_heartbeat = session.heartbeats.last
      is_valid_secret = @monitor.valid_secret?(last_heartbeat&.user_agent)

      course_user = course_users_hash[session.creator_id]

      snapshot = {
        sessionId: session.id,
        status: session.status,
        misses: session.misses,
        lastHeartbeatAt: last_heartbeat&.generated_at,
        isValid: is_valid_secret,
        userName: course_user.name,
        submissionId: submission_ids_hash[session.creator_id],
        stale: last_heartbeat&.stale
      }.compact

      [session.creator_id, snapshot]
    end
  end

  def groups
    current_course.groups.ordered_by_name.includes(:group_category, :course_users).map do |group|
      {
        id: group.id,
        name: group.name,
        category: group.group_category.name,
        userIds: group.course_users&.filter_map { |course_user| course_user.user_id if course_user.student? }
      }
    end
  end

  def broadcast(action, payload)
    Course::Monitoring::LiveMonitoringChannel.broadcast_from @monitor, action, payload
  end

  def broadcast_watch(users, snapshots, groups)
    broadcast :watch, {
      userIds: users,
      snapshots: snapshots,
      groups: groups,
      monitor: {
        maxIntervalMs: @monitor.max_interval_ms,
        offsetMs: @monitor.offset_ms,
        hasSecret: @monitor.secret?
      }
    }
  end

  def broadcast_viewed(recent_heartbeats)
    broadcast :viewed, recent_heartbeats
  end

  def component
    current_component_host[:course_monitoring_component]
  end

  def course_users_hash
    @course_users_hash ||= preload_course_users_hash(current_course)
  end

  def submission_ids_hash
    @submission_ids_hash ||= @monitor.assessment.submissions.to_h do |submission|
      [submission.creator_id, submission.id]
    end
  end
end