pupilfirst/pupilfirst

View on GitHub
app/presenters/courses/curriculum_presenter.rb

Summary

Maintainability
B
6 hrs
Test Coverage
A
100%
module Courses
  class CurriculumPresenter < ApplicationPresenter
    def initialize(view_context, course)
      @course = course
      super(view_context)
    end

    def page_title
      "#{@course.name} | #{current_school.name}"
    end

    private

    def props
      if current_student.present?
        {
          submissions: submissions,
          targets_read: targets_read,
          student: student_details,
          coaches: faculty.map(&:attributes),
          users: users,
          evaluation_criteria: evaluation_criteria,
          preview: false,
          **default_props
        }
      else
        {
          submissions: [],
          targets_read: [],
          student: student_details_for_preview_mode,
          coaches: [],
          users: [],
          evaluation_criteria: [],
          preview: true,
          **default_props
        }
      end
    end

    def default_props
      {
        current_user: user_details,
        author: author?,
        course: course_details,
        levels: levels_details,
        target_groups: target_groups,
        targets: targets,
        access_locked_levels: access_locked_levels
      }
    end

    def user_details
      if current_user.present?
        {
          id: current_user.id,
          name: current_user.name,
          avatar_url: current_user.avatar_url(variant: :thumb),
          is_admin: current_school_admin.present?,
          is_author: @course.course_authors.exists?(user: current_user),
          is_coach: @course.faculty.exists?(user: current_user),
          is_student: current_student.present?
        }
      else
        user_details_for_preview_mode
      end
    end

    def user_details_for_preview_mode
      {
        id: "-1",
        name: current_user&.name || "John Doe",
        avatar_url: nil,
        is_admin: false,
        is_author: false,
        is_coach: false,
        is_student: false
      }
    end

    def author?
      return false if current_user.blank?

      current_school_admin.present? ||
        @course.course_authors.exists?(user: current_user)
    end

    def evaluation_criteria
      @course.evaluation_criteria.map do |ec|
        ec.attributes.slice("id", "name", "max_grade", "grade_labels")
      end
    end

    def student_details_for_preview_mode
      {
        name: current_user&.name || "John Doe",
        level_id: levels.first.id,
        ends_at: nil
      }
    end

    def student_details
      {
        name: current_student.name,
        level_id: level_id_for_student,
        ends_at: current_student.cohort.ends_at,
        completed_at: current_student.completed_at
      }
    end

    def level_id_for_student
      if current_student&.timeline_events.blank?
        return levels.where.not(number: 0).last.id
      end

      current_student.timeline_events.last.target.level.id
    end

    def course_details
      details =
        @course.attributes.slice(
          "id",
          "progression_behavior",
          "progression_limit"
        )

      if current_user.present?
        details.merge(
          certificate_serial_number:
            current_user
              .issued_certificates
              .live
              .joins(:course)
              .find_by(courses: { id: @course.id })
              &.serial_number,
          ended: @course.ended?
        )
      else
        details.merge(ended: @course.ended?)
      end
    end

    def levels
      @levels ||= @course.levels.order(number: :DESC).load
    end

    def levels_details
      levels.map do |level|
        level.attributes.slice("id", "name", "number", "unlock_at")
      end
    end

    def target_groups
      scope =
        @course
          .target_groups
          .where(level_id: open_level_ids)
          .where(archived: false)

      scope.map do |target_group|
        target_group.attributes.slice(
          "id",
          "level_id",
          "name",
          "description",
          "sort_index",
          "milestone"
        )
      end
    end

    def targets
      attributes = %w[id title target_group_id sort_index]

      scope =
        @course
          .targets
          .live
          .joins(:target_group)
          .includes(
            assignments: %i[evaluation_criteria prerequisite_assignments]
          )
          .where(target_groups: { level_id: open_level_ids })
          .where(archived: false)

      scope
        .select(*attributes)
        .map do |target|
          details = target.attributes.slice(*attributes)
          assignment = target.assignments.not_archived.first
          if assignment
            details[:role] = assignment.role
            details[:resubmittable] = assignment.checklist.present?
            details[:milestone] = assignment.milestone
            details[:reviewed] = assignment.evaluation_criteria.present?
            details[:has_assignment] = true
            details[
              :prerequisite_target_ids
            ] = assignment.prerequisite_assignments.pluck(:target_id)
          else
            details[:role] = Assignment::ROLE_STUDENT
            details[:resubmittable] = false
            details[:milestone] = false
            details[:reviewed] = false
            details[:has_assignment] = false
            details[:prerequisite_target_ids] = []
          end
          details
        end
    end

    def submissions
      current_student
        .latest_submissions
        .includes(target: :assignments)
        .map do |submission|
          if submission.target.individual_target? ||
               submission.student_ids.sort == current_student.team_student_ids
            submission.attributes.slice(
              "target_id",
              "passed_at",
              "evaluated_at"
            )
          end
        end - [nil]
    end

    def targets_read
      current_student.page_reads.pluck(:target_id).map(&:to_s)
    end

    def faculty
      @faculty ||=
        begin
          scope = Faculty.left_joins(:students, :courses)

          scope
            .where(students: { id: current_student })
            .or(scope.where(courses: { id: @course }))
            .distinct
            .select(:id, :user_id)
            .load
        end
    end

    def team_members_user_ids
      @team_members_user_ids ||=
        if current_student.team.present?
          current_student.team.students.pluck(:user_id)
        else
          [current_student.user_id]
        end
    end

    def users
      user_ids = (team_members_user_ids + faculty.pluck(:user_id)).uniq

      User
        .where(id: user_ids)
        .with_attached_avatar
        .map do |user|
          details = user.attributes.slice("id", "name", "title")
          details["avatar_url"] = user.avatar_url(variant: :thumb)
          details
        end
    end

    def current_student
      @current_student ||=
        if current_user.present?
          @course.students.not_dropped_out.find_by(user_id: current_user.id)
        else
          nil
        end
    end

    # Admins and coaches who have review access in this course have access to locked levels as well.
    def access_locked_levels
      @access_locked_levels ||=
        if current_user.present?
          current_school_admin.present? ||
            (
              current_user.faculty.present? &&
                CoursePolicy.new(pundit_user, @course).review?
            )
        else
          false
        end
    end

    def open_level_ids
      @open_level_ids ||=
        begin
          scope = @course.levels

          if access_locked_levels
            scope
          else
            scope.where(unlock_at: nil).or(
              @course.levels.where("unlock_at <= ?", Time.zone.now)
            )
          end.pluck(:id)
        end
    end
  end
end