<commit_before>#
# Copyright (C) 2013 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

define [
  'jquery'
  'react'
  'react-dom'
  'ic-ajax'
  'compiled/util/round'
  'compiled/userSettings'
  '../../shared/xhr/fetch_all_pages'
  '../../shared/xhr/parse_link_header'
  'i18n!sr_gradebook'
  'ember'
  'underscore'
  'timezone'
  'compiled/AssignmentDetailsDialog'
  'compiled/AssignmentMuter'
  'jsx/gradebook/CourseGradeCalculator'
  'jsx/gradebook/EffectiveDueDates'
  'compiled/gradebook/OutcomeGradebookGrid'
  '../../shared/components/ic_submission_download_dialog_component'
  'str/htmlEscape'
  'compiled/models/grade_summary/CalculationMethodContent'
  'jsx/gradebook/SubmissionStateMap'
  'compiled/api/gradingPeriodsApi'
  'compiled/api/gradingPeriodSetsApi'
  'jsx/gradezilla/individual-gradebook/components/GradebookSelector'
  'jquery.instructure_date_and_time'
], (
  $, React, ReactDOM, 
  ajax, round, userSettings, fetchAllPages, parseLinkHeader, I18n, Ember, _, tz, AssignmentDetailsDialog,
  AssignmentMuter, CourseGradeCalculator, EffectiveDueDates, outcomeGrid, ic_submission_download_dialog,
  htmlEscape, CalculationMethodContent, SubmissionStateMap, GradingPeriodsApi, GradingPeriodSetsApi,
  GradebookSelector
) ->

  { get, set, setProperties } = Ember

  # http://emberjs.com/guides/controllers/
  # http://emberjs.com/api/classes/Ember.Controller.html
  # http://emberjs.com/api/classes/Ember.ArrayController.html
  # http://emberjs.com/api/classes/Ember.ObjectController.html

  gradingPeriodIsClosed = (gradingPeriod) ->
    new Date(gradingPeriod.close_date) < new Date()

  studentsUniqByEnrollments = (args...)->
    hiddenNameCounter = 1
    options =
      initialize: (array, changeMeta, instanceMeta) ->
        instanceMeta.students = {}
      addedItem: (array, enrollment, changeMeta, iMeta) ->
        student = iMeta.students[enrollment.user_id] or enrollment.user
        if !student.hiddenName?
          student.hiddenName = I18n.t("student_hidden_name", "Student %{position}", {position: hiddenNameCounter})
          hiddenNameCounter += 1
        student.sections ||= []
        student.sections.push(enrollment.course_section_id)
        student.role ||= enrollment.role
        return array if iMeta.students[student.id]
        iMeta.students[student.id] = student
        array.pushObject(student)
        array
      removedItem: (array, enrollment, _, instanceMeta)->
        student = array.findBy('id', enrollment.user_id)
        student.sections.removeObject(enrollment.course_section_id)

        if student.sections.length is 0
          delete instanceMeta.students[student.id]
          array.removeObject(student)
          hiddenNameCounter -= 1
        array
    args.push options
    Ember.arrayComputed.apply(null, args)

  contextUrl = get(window, 'ENV.GRADEBOOK_OPTIONS.context_url')

  ScreenreaderGradebookController = Ember.ObjectController.extend
    init: ->
      @set('effectiveDueDates', Ember.ObjectProxy.create(content: {}))
      @set('assignmentsFromGroups', Ember.ArrayProxy.create(content: [], isLoaded: false))

    checkForCsvExport: (->
      currentProgress = get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress')
      attachment = get(window, 'ENV.GRADEBOOK_OPTIONS.attachment')

      if currentProgress &&
         (currentProgress.progress.workflow_state != 'completed' &&
          currentProgress.progress.workflow_state != 'failed')

        attachmentProgress =
          progress_id: currentProgress.progress.id
          attachment_id: attachment.attachment.id

        $('#gradebook-export').prop('disabled', true)
        $('#last-exported-gradebook').hide()
        @pollGradebookCsvProgress(attachmentProgress)
    ).on('init')

    contextUrl: contextUrl
    uploadCsvUrl: "#{contextUrl}/gradebook_upload/new"

    lastGeneratedCsvAttachmentUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.attachment_url')

    downloadOutcomeCsvUrl: "#{contextUrl}/outcome_rollups.csv"

    gradingHistoryUrl:"#{contextUrl}/gradebook/history"

    submissionsUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.submissions_url')

    has_grading_periods: get(window, 'ENV.GRADEBOOK_OPTIONS.grading_period_set')?

    gradingPeriods:
      (->
        periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
        deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
        optionForAllPeriods =
          id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
        _.compact([optionForAllPeriods].concat(deserializedPeriods))
      )()

    getGradingPeriodSet: ->
      grading_period_set = get(window, 'ENV.GRADEBOOK_OPTIONS.grading_period_set')
      if grading_period_set
        GradingPeriodSetsApi.deserializeSet(grading_period_set)
      else
        null

    gradingPeriods: (->
      periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
      deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
      optionForAllPeriods =
        id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
      _.compact([optionForAllPeriods].concat(deserializedPeriods))
    )()

    lastGeneratedCsvLabel:  do () =>
      if get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress')
        gradebook_csv_export_date = get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress.progress.updated_at')
        I18n.t('Download Scores Generated on %{date}',
          {date: $.datetimeString(gradebook_csv_export_date)})

    selectedGradingPeriod: ((key, newValue) ->
      savedGradingPeriodId = userSettings.contextGet('gradebook_current_grading_period')
      if savedGradingPeriodId
        savedGP = @get('gradingPeriods').findBy('id', savedGradingPeriodId)
      if newValue
        userSettings.contextSet('gradebook_current_grading_period', newValue.id)
        newValue
      else if savedGP?
        savedGP
      else
        # default to current grading period, but don't change saved setting
        @get('gradingPeriods').findBy('id', ENV.GRADEBOOK_OPTIONS.current_grading_period_id)
    ).property()

    speedGraderUrl: (->
      "#{contextUrl}/gradebook/speed_grader?assignment_id=#{@get('selectedAssignment.id')}"
    ).property('selectedAssignment')

    studentUrl: (->
      "#{contextUrl}/grades/#{@get('selectedStudent.id')}"
    ).property('selectedStudent')

    showTotalAsPoints: (->
      ENV.GRADEBOOK_OPTIONS.show_total_grade_as_points
    ).property()

    publishToSisEnabled: (->
      ENV.GRADEBOOK_OPTIONS.publish_to_sis_enabled
    ).property()

    publishToSisURL:(->
      ENV.GRADEBOOK_OPTIONS.publish_to_sis_url
    ).property()

    teacherNotes: (->
      ENV.GRADEBOOK_OPTIONS.teacher_notes
    ).property().volatile()

    changeGradebookVersionUrl: (->
      "#{get(window, 'ENV.GRADEBOOK_OPTIONS.change_gradebook_version_url')}"
    ).property()

    hideOutcomes: (->
      !get(window, 'ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled')
    ).property()

    gradezilla: (->
      get window, 'ENV.GRADEBOOK_OPTIONS.gradezilla'
    ).property()

    showDownloadSubmissionsButton: (->
      hasSubmittedSubmissions     = @get('selectedAssignment.has_submitted_submissions')
      whitelist                   = ['online_upload','online_text_entry', 'online_url']
      submissionTypes             = @get('selectedAssignment.submission_types')
      submissionTypesOnWhitelist  = _.intersection(submissionTypes, whitelist)

      hasSubmittedSubmissions and _.any(submissionTypesOnWhitelist)
    ).property('selectedAssignment')

    hideStudentNames: false

    showConcludedEnrollments: (->
      userSettings.contextGet('show_concluded_enrollments') or false
    ).property().volatile()

    updateshowConcludedEnrollmentsSetting: ( ->
      isChecked = @get('showConcludedEnrollments')
      if isChecked?
        userSettings.contextSet 'show_concluded_enrollments', isChecked
    ).observes('showConcludedEnrollments')

    selectedAssignmentPointsPossible: ( ->
      I18n.n @get('selectedAssignment.points_possible')
    ).property('selectedAssignment')

    selectedStudent: null

    selectedSection: null

    selectedAssignment: null

    weightingScheme: null

    ariaAnnounced: null

    assignmentInClosedGradingPeriod: null

    disableAssignmentGrading: null

    actions:

      columnUpdated: (columnData, columnID) ->
        @updateColumnData columnData, columnID

      exportGradebookCsv: () ->
        $('#gradebook-export').prop('disabled', true)
        $('#last-exported-gradebook').hide()

        $.ajaxJSON(ENV.GRADEBOOK_OPTIONS.export_gradebook_csv_url, 'GET')
         .then((attachment_progress) => @pollGradebookCsvProgress(attachment_progress))

      gradeUpdated: (submissions) ->
        @updateSubmissionsFromExternal submissions

      selectItem: (property, item) ->
        @announce property, item

    pollGradebookCsvProgress: (attachmentProgress) ->
      self = this
      pollingProgress = setInterval(() =>
        $.ajaxJSON("/api/v1/progress/#{attachmentProgress.progress_id}", 'GET')
        .then((response) ->
          if response.workflow_state == 'completed'
            $.ajaxJSON("/api/v1/users/#{ENV.current_user_id}/files/#{attachmentProgress.attachment_id}", 'GET')
            .then((attachment) ->
              self.updateGradebookExportOptions(pollingProgress)
              document.getElementById('gradebook-export-iframe').src = attachment.url
              $('#last-exported-gradebook').attr('href', attachment.url)
            )

          if response.workflow_state == 'failed'
            self.updateGradebookExportOptions(pollingProgress)
        )
      , 2000)

    updateGradebookExportOptions: (pollingProgress) =>
      clearInterval pollingProgress
      $('#gradebook-export').prop('disabled', false)
      $('#last-exported-gradebook').show()

    announce: (prop, item) ->
      Ember.run.next =>
        if prop is 'student' and @get('hideStudentNames')
          text_to_announce = get item, 'hiddenName'
        else if prop is 'outcome'
          text_to_announce = get item, 'title'
        else
          text_to_announce = get item, 'name'
        @set 'ariaAnnounced', text_to_announce

    hideStudentNamesChanged: (->
      @set 'ariaAnnounced', null
    ).observes('hideStudentNames')

    setupSubmissionCallback: (->
      Ember.$.subscribe 'submissions_updated', _.bind(@updateSubmissionsFromExternal, this)
    ).on('init')

    setupAssignmentWeightingScheme: (->
      @set 'weightingScheme', ENV.GRADEBOOK_OPTIONS.group_weighting_scheme
    ).on('init')

    renderGradebookMenu: (->
      return unless @get('gradezilla')
      mountPoint = document.querySelector('[data-component="GradebookSelector"]')
      return unless mountPoint
      props =
        courseUrl: ENV.GRADEBOOK_OPTIONS.context_url
        learningMasteryEnabled: ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled
      component = React.createElement(GradebookSelector, props)
      ReactDOM.render(component, mountPoint)
    ).on('init')

    willDestroy: ->
      Ember.$.unsubscribe 'submissions_updated'
      @_super()

    updateSubmissionsFromExternal: (submissions) ->
      subs_proxy = @get('submissions')
      selected = @get('selectedSubmission')
      studentsById = @groupById @get('students')
      assignmentsById = @groupById @get('assignments')
      submissions.forEach (submission) =>
        student = studentsById[submission.user_id]
        submissionsForStudent = subs_proxy.findBy('user_id', submission.user_id)
        oldSubmission = submissionsForStudent.submissions.findBy('assignment_id', submission.assignment_id)

        #check for DA visibility
        if submission.assignment_visible?
          set(submission, 'hidden', !submission.assignment_visible)
          @updateAssignmentVisibilities(assignmentsById[submission.assignment_id], submission.user_id)

        submissionsForStudent.submissions.removeObject oldSubmission
        submissionsForStudent.submissions.addObject submission
        @updateSubmission submission, student
        @calculateStudentGrade student
        if selected and selected.assignment_id == submission.assignment_id and selected.user_id == submission.user_id
          set(this, 'selectedSubmission', submission)

    updateAssignmentVisibilities: (assignment, userId) ->
      visibilities = get(assignment, 'assignment_visibility')
      filteredVisibilities = visibilities?.filter (id) ->
        id != userId
      set(assignment, 'assignment_visibility', filteredVisibilities)

    subtotalByGradingPeriod: ->
      selectedPeriodID = @get('selectedGradingPeriod.id')
      selectedPeriodID and selectedPeriodID == '0' and @periodsAreWeighted()

    calculate: (student) ->
      submissions = @submissionsForStudent(student)
      assignmentGroups = @assignmentGroupsHash()
      weightingScheme = @get('weightingScheme')
      gradingPeriodSet = @getGradingPeriodSet()
      effectiveDueDates = @get('effectiveDueDates.content')

      hasGradingPeriods = gradingPeriodSet and effectiveDueDates

      CourseGradeCalculator.calculate(
        submissions,
        assignmentGroups,
        weightingScheme,
        gradingPeriodSet if hasGradingPeriods,
        EffectiveDueDates.scopeToUser(effectiveDueDates, student.id) if hasGradingPeriods
      )

    submissionsForStudent: (student) ->
      allSubmissions = (value for key, value of student when key.match /^assignment_(?!group)/)
      return allSubmissions unless @get('has_grading_periods')
      selectedPeriodID = @get('selectedGradingPeriod.id')
      return allSubmissions if !selectedPeriodID or selectedPeriodID == '0'

      _.filter allSubmissions, (submission) =>
        studentPeriodInfo = @get('effectiveDueDates').get(submission.assignment_id)?[submission.user_id]
        studentPeriodInfo and studentPeriodInfo.grading_period_id == selectedPeriodID

    calculateSingleGrade: (student, key, gradeFinalOrCurrent) ->
      set(student, key, gradeFinalOrCurrent)
      if gradeFinalOrCurrent?.submissions
        for submissionData in gradeFinalOrCurrent.submissions
          set(submissionData.submission, 'drop', submissionData.drop)

    calculateStudentGrade: (student) ->
      if student.isLoaded
        grades = @calculate(student)

        selectedPeriodID = @get('selectedGradingPeriod.id')
        if selectedPeriodID && selectedPeriodID != '0'
          grades = grades.gradingPeriods[selectedPeriodID]

        finalOrCurrent = if @get('includeUngradedAssignments') then 'final' else 'current'

        if @subtotalByGradingPeriod()
          for gradingPeriodId, grade of grades.gradingPeriods
            @calculateSingleGrade(student, "grading_period_#{gradingPeriodId}", grade[finalOrCurrent])
        else
          for assignmentGroupId, grade of grades.assignmentGroups
            @calculateSingleGrade(student, "assignment_group_#{assignmentGroupId}", grade[finalOrCurrent])

        grades = grades[finalOrCurrent]

        percent = round (grades.score / grades.possible * 100), 2
        percent = 0 if isNaN(percent)
        setProperties student,
          total_grade: grades
          total_percent: percent

    calculateAllGrades: (->
      @get('students').forEach (student) => @calculateStudentGrade student
    ).observes(
      'includeUngradedAssignments','groupsAreWeighted', 'assignment_groups.@each.group_weight',
      'selectedGradingPeriod', 'gradingPeriods.@each.weight'
    )

    sectionSelectDefaultLabel: I18n.t "all_sections", "All Sections"
    studentSelectDefaultLabel: I18n.t "no_student", "No Student Selected"
    assignmentSelectDefaultLabel: I18n.t "no_assignment", "No Assignment Selected"
    outcomeSelectDefaultLabel: I18n.t "no_outcome", "No Outcome Selected"

    submissionStateMap: null

    assignment_groups: []
    assignment_subtotals: []
    subtotal_by_period: false

    fetchAssignmentGroups: (->
      params = { exclude_response_fields: ['in_closed_grading_period'] }
      gpId = @get('selectedGradingPeriod.id')
      if @get('has_grading_periods') && gpId != '0'
        params.grading_period_id = gpId
      @set('assignment_groups', [])
      @get('assignmentsFromGroups').setProperties(content: [], isLoaded: false)
      Ember.run.once =>
        fetchAllPages(get(window, 'ENV.GRADEBOOK_OPTIONS.assignment_groups_url'), { data: params, records: @get('assignment_groups') })
    ).observes('selectedGradingPeriod').on('init')

    pushAssignmentGroups: (subtotals) ->
      @set('subtotal_by_period', false)
      weighted = @get('groupsAreWeighted')
      for group in @get('assignment_groups')
        subtotal =
          name: group.name
          key: "assignment_group_#{group.id}"
          weight: weighted && group.group_weight
        subtotals.push(subtotal)

    pushGradingPeriods: (subtotals) ->
      @set('subtotal_by_period', true)
      weighted = @periodsAreWeighted()
      for period in @get('gradingPeriods')
        if period.id > 0
          subtotal =
            name: period.title
            key: "grading_period_#{period.id}"
            weight: weighted && period.weight
          subtotals.push(subtotal)

    assignmentSubtotals: (->
      subtotals = []
      if @subtotalByGradingPeriod()
        @pushGradingPeriods(subtotals)
      else
        @pushAssignmentGroups(subtotals)
      @set('assignment_subtotals', subtotals)
    ).observes('assignment_groups', 'gradingPeriods', 'selectedGradingPeriod', 'students.@each', 'selectedStudent').on('init')

    students: studentsUniqByEnrollments('enrollments')

    studentsHash: ->
      students = {}
      @get('students').forEach (s) ->
        unless s.role == "StudentViewEnrollment"
          students[s.id] = s
      students

    processLoadingSubmissions: (submissionGroups) ->
      submissions = []

      _.forEach submissionGroups, (submissionGroup) =>
        submissions.push(submissionGroup.submissions...)

      @updateEffectiveDueDatesFromSubmissions(submissions)
      assignmentIds = _.uniq(_.pluck(submissions, 'assignment_id'))
      assignmentMap = _.indexBy(@get('assignmentsFromGroups.content'), 'id')
      assignmentIds.forEach (assignmentId) =>
        assignment = assignmentMap[assignmentId]
        @updateEffectiveDueDatesOnAssignment(assignment) if assignment

      submissionGroups

    fetchStudentSubmissions: (->
      Ember.run.once =>
        notYetLoaded = @get('students').filter (student) ->
          return false if get(student, 'isLoaded') or get(student, 'isLoading')
          set(student, 'isLoading', true)
          student

        return unless notYetLoaded.length
        studentIds = notYetLoaded.mapBy('id')

        while (studentIds.length)
          chunk = studentIds.splice(0, ENV.GRADEBOOK_OPTIONS.chunk_size || 20)
          fetchAllPages(
            ENV.GRADEBOOK_OPTIONS.submissions_url,
            {
              data: { student_ids: chunk },
              process: (submissions) => @processLoadingSubmissions(submissions),
              records: @get('submissions')
            }
          )

    ).observes('students.@each', 'selectedGradingPeriod').on('init')

    showNotesColumn: (->
      notes = @get('teacherNotes')
      if notes
        !notes.hidden
      else
        false
    ).property().volatile()

    shouldCreateNotes: (->
      !@get('teacherNotes') and @get('showNotesColumn')
    ).property('teacherNotes', 'showNotesColumn', 'custom_columns.@each')

    notesURL: (->
      if @get('shouldCreateNotes')
        window.ENV.GRADEBOOK_OPTIONS.custom_columns_url
      else
        notesID = @get('teacherNotes')?.id
        window.ENV.GRADEBOOK_OPTIONS.custom_column_url.replace(/:id/, notesID)
    ).property('shouldCreateNotes', 'custom_columns.@each')

    notesParams: (->
      if @get('shouldCreateNotes')
        "column[title]": I18n.t("notes", "Notes")
        "column[position]": 1
        "column[teacher_notes]": true
      else
        "column[hidden]": !@get('showNotesColumn')
    ).property('shouldCreateNotes', 'showNotesColumn')

    notesVerb: (->
      if @get('shouldCreateNotes') then "POST" else "PUT"
    ).property('shouldCreateNotes')

    updateOrCreateNotesColumn: (->
      ajax.request(
        dataType: "json"
        type: @get('notesVerb')
        url: @get('notesURL')
        data: @get('notesParams')
      ).then @boundNotesSuccess
    ).observes('showNotesColumn')

    bindNotesSuccess:(->
      @boundNotesSuccess = _.bind(@onNotesUpdateSuccess, this)
    ).on('init')

    onNotesUpdateSuccess: (col) ->
      customColumns = @get('custom_columns')
      method = if col.hidden then 'removeObject' else 'unshiftObject'
      column = customColumns.findBy('id', col.id) or col
      customColumns[method] column

      if col.teacher_notes
        @set 'teacherNotes', col

      unless col.hidden
        ajax.request(
          url: ENV.GRADEBOOK_OPTIONS.reorder_custom_columns_url
          type:"POST"
          data:
            order: customColumns.mapBy('id')
        )

    groupsAreWeighted: (->
      @get("weightingScheme") == "percent"
    ).property("weightingScheme")

    periodsAreWeighted: ->
      !!@getGradingPeriodSet()?.weighted

    gradesAreWeighted: (->
      @get('groupsAreWeighted') or @periodsAreWeighted()
    ).property('weightingScheme')

    hidePointsPossibleForFinalGrade: (->
      !!(@get('groupsAreWeighted') or @subtotalByGradingPeriod())
    ).property("weightingScheme", "selectedGradingPeriod")

    updateShowTotalAs: (->
      @set "showTotalAsPoints", @get("showTotalAsPoints")
      ajax.request(
        dataType: "json"
        type: "PUT"
        url: ENV.GRADEBOOK_OPTIONS.setting_update_url
        data:
          show_total_grade_as_points: @get("showTotalAsPoints"))
    ).observes('showTotalAsPoints', 'gradesAreWeighted')

    studentColumnData: {}

    updateColumnData: (columnDatum, columnID) ->
      studentData = @get('studentColumnData')
      dataForStudent = studentData[columnDatum.user_id] or Ember.A()

      columnForStudent = dataForStudent.findBy('column_id', columnID)
      if columnForStudent
        columnForStudent.set 'content', columnDatum.content
      else
        dataForStudent.push Ember.Object.create
                              column_id: columnID
                              content: columnDatum.content
      studentData[columnDatum.user_id] = dataForStudent

    fetchColumnData: (col, url) ->
      url ||= ENV.GRADEBOOK_OPTIONS.custom_column_data_url.replace /:id/, col.id
      ajax.raw(url, {dataType:"json"}).then (result) =>
        for datum in result.response
          @updateColumnData datum, col.id
        meta = parseLinkHeader result.jqXHR
        if meta.next
          @fetchColumnData col, meta.next
        else
          setProperties col,
            'isLoading': false
            'isLoaded': true

    dataForStudent: (->
      selectedStudent = @get('selectedStudent')
      return unless selectedStudent?
      @get('studentColumnData')[selectedStudent.id]
    ).property('selectedStudent', 'custom_columns.@each.isLoaded')

    loadCustomColumnData: (->
      return unless (@get('enrollments.isLoaded'))
      @get('custom_columns').filter((col) ->
        return false if get(col, 'isLoaded') or get(col, 'isLoading')
        set col, 'isLoading', true
        col
      ).forEach (col) =>
        @fetchColumnData col
    ).observes('enrollments.isLoaded', 'custom_columns.@each')

    studentsInSelectedSection: (->
      students = @get('students')
      currentSection = @get('selectedSection')

      return students if not currentSection
      students.filter (s) -> s.sections.contains(currentSection.id)
    ).property('students.@each', 'selectedSection')

    groupById: (array) ->
      array.reduce( (obj, item) ->
        obj[get(item, 'id')] = item
        obj
      ,{})

    submissionsLoaded: (->
      assignments = @get("assignments")
      assignmentsByID = @groupById assignments
      studentsByID = @groupById @get("students")
      submissions = @get('submissions')
      submissions.forEach ((submission) -><commit_msg>update submissions using all assignments<commit_after>#
# Copyright (C) 2013 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

define [
  'jquery'
  'react'
  'react-dom'
  'ic-ajax'
  'compiled/util/round'
  'compiled/userSettings'
  '../../shared/xhr/fetch_all_pages'
  '../../shared/xhr/parse_link_header'
  'i18n!sr_gradebook'
  'ember'
  'underscore'
  'timezone'
  'compiled/AssignmentDetailsDialog'
  'compiled/AssignmentMuter'
  'jsx/gradebook/CourseGradeCalculator'
  'jsx/gradebook/EffectiveDueDates'
  'compiled/gradebook/OutcomeGradebookGrid'
  '../../shared/components/ic_submission_download_dialog_component'
  'str/htmlEscape'
  'compiled/models/grade_summary/CalculationMethodContent'
  'jsx/gradebook/SubmissionStateMap'
  'compiled/api/gradingPeriodsApi'
  'compiled/api/gradingPeriodSetsApi'
  'jsx/gradezilla/individual-gradebook/components/GradebookSelector'
  'jquery.instructure_date_and_time'
], (
  $, React, ReactDOM, 
  ajax, round, userSettings, fetchAllPages, parseLinkHeader, I18n, Ember, _, tz, AssignmentDetailsDialog,
  AssignmentMuter, CourseGradeCalculator, EffectiveDueDates, outcomeGrid, ic_submission_download_dialog,
  htmlEscape, CalculationMethodContent, SubmissionStateMap, GradingPeriodsApi, GradingPeriodSetsApi,
  GradebookSelector
) ->

  { get, set, setProperties } = Ember

  # http://emberjs.com/guides/controllers/
  # http://emberjs.com/api/classes/Ember.Controller.html
  # http://emberjs.com/api/classes/Ember.ArrayController.html
  # http://emberjs.com/api/classes/Ember.ObjectController.html

  gradingPeriodIsClosed = (gradingPeriod) ->
    new Date(gradingPeriod.close_date) < new Date()

  studentsUniqByEnrollments = (args...)->
    hiddenNameCounter = 1
    options =
      initialize: (array, changeMeta, instanceMeta) ->
        instanceMeta.students = {}
      addedItem: (array, enrollment, changeMeta, iMeta) ->
        student = iMeta.students[enrollment.user_id] or enrollment.user
        if !student.hiddenName?
          student.hiddenName = I18n.t("student_hidden_name", "Student %{position}", {position: hiddenNameCounter})
          hiddenNameCounter += 1
        student.sections ||= []
        student.sections.push(enrollment.course_section_id)
        student.role ||= enrollment.role
        return array if iMeta.students[student.id]
        iMeta.students[student.id] = student
        array.pushObject(student)
        array
      removedItem: (array, enrollment, _, instanceMeta)->
        student = array.findBy('id', enrollment.user_id)
        student.sections.removeObject(enrollment.course_section_id)

        if student.sections.length is 0
          delete instanceMeta.students[student.id]
          array.removeObject(student)
          hiddenNameCounter -= 1
        array
    args.push options
    Ember.arrayComputed.apply(null, args)

  contextUrl = get(window, 'ENV.GRADEBOOK_OPTIONS.context_url')

  ScreenreaderGradebookController = Ember.ObjectController.extend
    init: ->
      @set('effectiveDueDates', Ember.ObjectProxy.create(content: {}))
      @set('assignmentsFromGroups', Ember.ArrayProxy.create(content: [], isLoaded: false))

    checkForCsvExport: (->
      currentProgress = get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress')
      attachment = get(window, 'ENV.GRADEBOOK_OPTIONS.attachment')

      if currentProgress &&
         (currentProgress.progress.workflow_state != 'completed' &&
          currentProgress.progress.workflow_state != 'failed')

        attachmentProgress =
          progress_id: currentProgress.progress.id
          attachment_id: attachment.attachment.id

        $('#gradebook-export').prop('disabled', true)
        $('#last-exported-gradebook').hide()
        @pollGradebookCsvProgress(attachmentProgress)
    ).on('init')

    contextUrl: contextUrl
    uploadCsvUrl: "#{contextUrl}/gradebook_upload/new"

    lastGeneratedCsvAttachmentUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.attachment_url')

    downloadOutcomeCsvUrl: "#{contextUrl}/outcome_rollups.csv"

    gradingHistoryUrl:"#{contextUrl}/gradebook/history"

    submissionsUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.submissions_url')

    has_grading_periods: get(window, 'ENV.GRADEBOOK_OPTIONS.grading_period_set')?

    gradingPeriods:
      (->
        periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
        deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
        optionForAllPeriods =
          id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
        _.compact([optionForAllPeriods].concat(deserializedPeriods))
      )()

    getGradingPeriodSet: ->
      grading_period_set = get(window, 'ENV.GRADEBOOK_OPTIONS.grading_period_set')
      if grading_period_set
        GradingPeriodSetsApi.deserializeSet(grading_period_set)
      else
        null

    gradingPeriods: (->
      periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
      deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
      optionForAllPeriods =
        id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
      _.compact([optionForAllPeriods].concat(deserializedPeriods))
    )()

    lastGeneratedCsvLabel:  do () =>
      if get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress')
        gradebook_csv_export_date = get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress.progress.updated_at')
        I18n.t('Download Scores Generated on %{date}',
          {date: $.datetimeString(gradebook_csv_export_date)})

    selectedGradingPeriod: ((key, newValue) ->
      savedGradingPeriodId = userSettings.contextGet('gradebook_current_grading_period')
      if savedGradingPeriodId
        savedGP = @get('gradingPeriods').findBy('id', savedGradingPeriodId)
      if newValue
        userSettings.contextSet('gradebook_current_grading_period', newValue.id)
        newValue
      else if savedGP?
        savedGP
      else
        # default to current grading period, but don't change saved setting
        @get('gradingPeriods').findBy('id', ENV.GRADEBOOK_OPTIONS.current_grading_period_id)
    ).property()

    speedGraderUrl: (->
      "#{contextUrl}/gradebook/speed_grader?assignment_id=#{@get('selectedAssignment.id')}"
    ).property('selectedAssignment')

    studentUrl: (->
      "#{contextUrl}/grades/#{@get('selectedStudent.id')}"
    ).property('selectedStudent')

    showTotalAsPoints: (->
      ENV.GRADEBOOK_OPTIONS.show_total_grade_as_points
    ).property()

    publishToSisEnabled: (->
      ENV.GRADEBOOK_OPTIONS.publish_to_sis_enabled
    ).property()

    publishToSisURL:(->
      ENV.GRADEBOOK_OPTIONS.publish_to_sis_url
    ).property()

    teacherNotes: (->
      ENV.GRADEBOOK_OPTIONS.teacher_notes
    ).property().volatile()

    changeGradebookVersionUrl: (->
      "#{get(window, 'ENV.GRADEBOOK_OPTIONS.change_gradebook_version_url')}"
    ).property()

    hideOutcomes: (->
      !get(window, 'ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled')
    ).property()

    gradezilla: (->
      get window, 'ENV.GRADEBOOK_OPTIONS.gradezilla'
    ).property()

    showDownloadSubmissionsButton: (->
      hasSubmittedSubmissions     = @get('selectedAssignment.has_submitted_submissions')
      whitelist                   = ['online_upload','online_text_entry', 'online_url']
      submissionTypes             = @get('selectedAssignment.submission_types')
      submissionTypesOnWhitelist  = _.intersection(submissionTypes, whitelist)

      hasSubmittedSubmissions and _.any(submissionTypesOnWhitelist)
    ).property('selectedAssignment')

    hideStudentNames: false

    showConcludedEnrollments: (->
      userSettings.contextGet('show_concluded_enrollments') or false
    ).property().volatile()

    updateshowConcludedEnrollmentsSetting: ( ->
      isChecked = @get('showConcludedEnrollments')
      if isChecked?
        userSettings.contextSet 'show_concluded_enrollments', isChecked
    ).observes('showConcludedEnrollments')

    selectedAssignmentPointsPossible: ( ->
      I18n.n @get('selectedAssignment.points_possible')
    ).property('selectedAssignment')

    selectedStudent: null

    selectedSection: null

    selectedAssignment: null

    weightingScheme: null

    ariaAnnounced: null

    assignmentInClosedGradingPeriod: null

    disableAssignmentGrading: null

    actions:

      columnUpdated: (columnData, columnID) ->
        @updateColumnData columnData, columnID

      exportGradebookCsv: () ->
        $('#gradebook-export').prop('disabled', true)
        $('#last-exported-gradebook').hide()

        $.ajaxJSON(ENV.GRADEBOOK_OPTIONS.export_gradebook_csv_url, 'GET')
         .then((attachment_progress) => @pollGradebookCsvProgress(attachment_progress))

      gradeUpdated: (submissions) ->
        @updateSubmissionsFromExternal submissions

      selectItem: (property, item) ->
        @announce property, item

    pollGradebookCsvProgress: (attachmentProgress) ->
      self = this
      pollingProgress = setInterval(() =>
        $.ajaxJSON("/api/v1/progress/#{attachmentProgress.progress_id}", 'GET')
        .then((response) ->
          if response.workflow_state == 'completed'
            $.ajaxJSON("/api/v1/users/#{ENV.current_user_id}/files/#{attachmentProgress.attachment_id}", 'GET')
            .then((attachment) ->
              self.updateGradebookExportOptions(pollingProgress)
              document.getElementById('gradebook-export-iframe').src = attachment.url
              $('#last-exported-gradebook').attr('href', attachment.url)
            )

          if response.workflow_state == 'failed'
            self.updateGradebookExportOptions(pollingProgress)
        )
      , 2000)

    updateGradebookExportOptions: (pollingProgress) =>
      clearInterval pollingProgress
      $('#gradebook-export').prop('disabled', false)
      $('#last-exported-gradebook').show()

    announce: (prop, item) ->
      Ember.run.next =>
        if prop is 'student' and @get('hideStudentNames')
          text_to_announce = get item, 'hiddenName'
        else if prop is 'outcome'
          text_to_announce = get item, 'title'
        else
          text_to_announce = get item, 'name'
        @set 'ariaAnnounced', text_to_announce

    hideStudentNamesChanged: (->
      @set 'ariaAnnounced', null
    ).observes('hideStudentNames')

    setupSubmissionCallback: (->
      Ember.$.subscribe 'submissions_updated', _.bind(@updateSubmissionsFromExternal, this)
    ).on('init')

    setupAssignmentWeightingScheme: (->
      @set 'weightingScheme', ENV.GRADEBOOK_OPTIONS.group_weighting_scheme
    ).on('init')

    renderGradebookMenu: (->
      return unless @get('gradezilla')
      mountPoint = document.querySelector('[data-component="GradebookSelector"]')
      return unless mountPoint
      props =
        courseUrl: ENV.GRADEBOOK_OPTIONS.context_url
        learningMasteryEnabled: ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled
      component = React.createElement(GradebookSelector, props)
      ReactDOM.render(component, mountPoint)
    ).on('init')

    willDestroy: ->
      Ember.$.unsubscribe 'submissions_updated'
      @_super()

    updateSubmissionsFromExternal: (submissions) ->
      subs_proxy = @get('submissions')
      selected = @get('selectedSubmission')
      studentsById = @groupById @get('students')
      assignmentsById = @groupById @get('assignments')
      submissions.forEach (submission) =>
        student = studentsById[submission.user_id]
        submissionsForStudent = subs_proxy.findBy('user_id', submission.user_id)
        oldSubmission = submissionsForStudent.submissions.findBy('assignment_id', submission.assignment_id)

        #check for DA visibility
        if submission.assignment_visible?
          set(submission, 'hidden', !submission.assignment_visible)
          @updateAssignmentVisibilities(assignmentsById[submission.assignment_id], submission.user_id)

        submissionsForStudent.submissions.removeObject oldSubmission
        submissionsForStudent.submissions.addObject submission
        @updateSubmission submission, student
        @calculateStudentGrade student
        if selected and selected.assignment_id == submission.assignment_id and selected.user_id == submission.user_id
          set(this, 'selectedSubmission', submission)

    updateAssignmentVisibilities: (assignment, userId) ->
      visibilities = get(assignment, 'assignment_visibility')
      filteredVisibilities = visibilities?.filter (id) ->
        id != userId
      set(assignment, 'assignment_visibility', filteredVisibilities)

    subtotalByGradingPeriod: ->
      selectedPeriodID = @get('selectedGradingPeriod.id')
      selectedPeriodID and selectedPeriodID == '0' and @periodsAreWeighted()

    calculate: (student) ->
      submissions = @submissionsForStudent(student)
      assignmentGroups = @assignmentGroupsHash()
      weightingScheme = @get('weightingScheme')
      gradingPeriodSet = @getGradingPeriodSet()
      effectiveDueDates = @get('effectiveDueDates.content')

      hasGradingPeriods = gradingPeriodSet and effectiveDueDates

      CourseGradeCalculator.calculate(
        submissions,
        assignmentGroups,
        weightingScheme,
        gradingPeriodSet if hasGradingPeriods,
        EffectiveDueDates.scopeToUser(effectiveDueDates, student.id) if hasGradingPeriods
      )

    submissionsForStudent: (student) ->
      allSubmissions = (value for key, value of student when key.match /^assignment_(?!group)/)
      return allSubmissions unless @get('has_grading_periods')
      selectedPeriodID = @get('selectedGradingPeriod.id')
      return allSubmissions if !selectedPeriodID or selectedPeriodID == '0'

      _.filter allSubmissions, (submission) =>
        studentPeriodInfo = @get('effectiveDueDates').get(submission.assignment_id)?[submission.user_id]
        studentPeriodInfo and studentPeriodInfo.grading_period_id == selectedPeriodID

    calculateSingleGrade: (student, key, gradeFinalOrCurrent) ->
      set(student, key, gradeFinalOrCurrent)
      if gradeFinalOrCurrent?.submissions
        for submissionData in gradeFinalOrCurrent.submissions
          set(submissionData.submission, 'drop', submissionData.drop)

    calculateStudentGrade: (student) ->
      if student.isLoaded
        grades = @calculate(student)

        selectedPeriodID = @get('selectedGradingPeriod.id')
        if selectedPeriodID && selectedPeriodID != '0'
          grades = grades.gradingPeriods[selectedPeriodID]

        finalOrCurrent = if @get('includeUngradedAssignments') then 'final' else 'current'

        if @subtotalByGradingPeriod()
          for gradingPeriodId, grade of grades.gradingPeriods
            @calculateSingleGrade(student, "grading_period_#{gradingPeriodId}", grade[finalOrCurrent])
        else
          for assignmentGroupId, grade of grades.assignmentGroups
            @calculateSingleGrade(student, "assignment_group_#{assignmentGroupId}", grade[finalOrCurrent])

        grades = grades[finalOrCurrent]

        percent = round (grades.score / grades.possible * 100), 2
        percent = 0 if isNaN(percent)
        setProperties student,
          total_grade: grades
          total_percent: percent

    calculateAllGrades: (->
      @get('students').forEach (student) => @calculateStudentGrade student
    ).observes(
      'includeUngradedAssignments','groupsAreWeighted', 'assignment_groups.@each.group_weight',
      'selectedGradingPeriod', 'gradingPeriods.@each.weight'
    )

    sectionSelectDefaultLabel: I18n.t "all_sections", "All Sections"
    studentSelectDefaultLabel: I18n.t "no_student", "No Student Selected"
    assignmentSelectDefaultLabel: I18n.t "no_assignment", "No Assignment Selected"
    outcomeSelectDefaultLabel: I18n.t "no_outcome", "No Outcome Selected"

    submissionStateMap: null

    assignment_groups: []
    assignment_subtotals: []
    subtotal_by_period: false

    fetchAssignmentGroups: (->
      params = { exclude_response_fields: ['in_closed_grading_period'] }
      gpId = @get('selectedGradingPeriod.id')
      if @get('has_grading_periods') && gpId != '0'
        params.grading_period_id = gpId
      @set('assignment_groups', [])
      @get('assignmentsFromGroups').setProperties(content: [], isLoaded: false)
      Ember.run.once =>
        fetchAllPages(get(window, 'ENV.GRADEBOOK_OPTIONS.assignment_groups_url'), { data: params, records: @get('assignment_groups') })
    ).observes('selectedGradingPeriod').on('init')

    pushAssignmentGroups: (subtotals) ->
      @set('subtotal_by_period', false)
      weighted = @get('groupsAreWeighted')
      for group in @get('assignment_groups')
        subtotal =
          name: group.name
          key: "assignment_group_#{group.id}"
          weight: weighted && group.group_weight
        subtotals.push(subtotal)

    pushGradingPeriods: (subtotals) ->
      @set('subtotal_by_period', true)
      weighted = @periodsAreWeighted()
      for period in @get('gradingPeriods')
        if period.id > 0
          subtotal =
            name: period.title
            key: "grading_period_#{period.id}"
            weight: weighted && period.weight
          subtotals.push(subtotal)

    assignmentSubtotals: (->
      subtotals = []
      if @subtotalByGradingPeriod()
        @pushGradingPeriods(subtotals)
      else
        @pushAssignmentGroups(subtotals)
      @set('assignment_subtotals', subtotals)
    ).observes('assignment_groups', 'gradingPeriods', 'selectedGradingPeriod', 'students.@each', 'selectedStudent').on('init')

    students: studentsUniqByEnrollments('enrollments')

    studentsHash: ->
      students = {}
      @get('students').forEach (s) ->
        unless s.role == "StudentViewEnrollment"
          students[s.id] = s
      students

    processLoadingSubmissions: (submissionGroups) ->
      submissions = []

      _.forEach submissionGroups, (submissionGroup) =>
        submissions.push(submissionGroup.submissions...)

      @updateEffectiveDueDatesFromSubmissions(submissions)
      assignmentIds = _.uniq(_.pluck(submissions, 'assignment_id'))
      assignmentMap = _.indexBy(@get('assignmentsFromGroups.content'), 'id')
      assignmentIds.forEach (assignmentId) =>
        assignment = assignmentMap[assignmentId]
        @updateEffectiveDueDatesOnAssignment(assignment) if assignment

      submissionGroups

    fetchStudentSubmissions: (->
      Ember.run.once =>
        notYetLoaded = @get('students').filter (student) ->
          return false if get(student, 'isLoaded') or get(student, 'isLoading')
          set(student, 'isLoading', true)
          student

        return unless notYetLoaded.length
        studentIds = notYetLoaded.mapBy('id')

        while (studentIds.length)
          chunk = studentIds.splice(0, ENV.GRADEBOOK_OPTIONS.chunk_size || 20)
          fetchAllPages(
            ENV.GRADEBOOK_OPTIONS.submissions_url,
            {
              data: { student_ids: chunk },
              process: (submissions) => @processLoadingSubmissions(submissions),
              records: @get('submissions')
            }
          )

    ).observes('students.@each', 'selectedGradingPeriod').on('init')

    showNotesColumn: (->
      notes = @get('teacherNotes')
      if notes
        !notes.hidden
      else
        false
    ).property().volatile()

    shouldCreateNotes: (->
      !@get('teacherNotes') and @get('showNotesColumn')
    ).property('teacherNotes', 'showNotesColumn', 'custom_columns.@each')

    notesURL: (->
      if @get('shouldCreateNotes')
        window.ENV.GRADEBOOK_OPTIONS.custom_columns_url
      else
        notesID = @get('teacherNotes')?.id
        window.ENV.GRADEBOOK_OPTIONS.custom_column_url.replace(/:id/, notesID)
    ).property('shouldCreateNotes', 'custom_columns.@each')

    notesParams: (->
      if @get('shouldCreateNotes')
        "column[title]": I18n.t("notes", "Notes")
        "column[position]": 1
        "column[teacher_notes]": true
      else
        "column[hidden]": !@get('showNotesColumn')
    ).property('shouldCreateNotes', 'showNotesColumn')

    notesVerb: (->
      if @get('shouldCreateNotes') then "POST" else "PUT"
    ).property('shouldCreateNotes')

    updateOrCreateNotesColumn: (->
      ajax.request(
        dataType: "json"
        type: @get('notesVerb')
        url: @get('notesURL')
        data: @get('notesParams')
      ).then @boundNotesSuccess
    ).observes('showNotesColumn')

    bindNotesSuccess:(->
      @boundNotesSuccess = _.bind(@onNotesUpdateSuccess, this)
    ).on('init')

    onNotesUpdateSuccess: (col) ->
      customColumns = @get('custom_columns')
      method = if col.hidden then 'removeObject' else 'unshiftObject'
      column = customColumns.findBy('id', col.id) or col
      customColumns[method] column

      if col.teacher_notes
        @set 'teacherNotes', col

      unless col.hidden
        ajax.request(
          url: ENV.GRADEBOOK_OPTIONS.reorder_custom_columns_url
          type:"POST"
          data:
            order: customColumns.mapBy('id')
        )

    groupsAreWeighted: (->
      @get("weightingScheme") == "percent"
    ).property("weightingScheme")

    periodsAreWeighted: ->
      !!@getGradingPeriodSet()?.weighted

    gradesAreWeighted: (->
      @get('groupsAreWeighted') or @periodsAreWeighted()
    ).property('weightingScheme')

    hidePointsPossibleForFinalGrade: (->
      !!(@get('groupsAreWeighted') or @subtotalByGradingPeriod())
    ).property("weightingScheme", "selectedGradingPeriod")

    updateShowTotalAs: (->
      @set "showTotalAsPoints", @get("showTotalAsPoints")
      ajax.request(
        dataType: "json"
        type: "PUT"
        url: ENV.GRADEBOOK_OPTIONS.setting_update_url
        data:
          show_total_grade_as_points: @get("showTotalAsPoints"))
    ).observes('showTotalAsPoints', 'gradesAreWeighted')

    studentColumnData: {}

    updateColumnData: (columnDatum, columnID) ->
      studentData = @get('studentColumnData')
      dataForStudent = studentData[columnDatum.user_id] or Ember.A()

      columnForStudent = dataForStudent.findBy('column_id', columnID)
      if columnForStudent
        columnForStudent.set 'content', columnDatum.content
      else
        dataForStudent.push Ember.Object.create
                              column_id: columnID
                              content: columnDatum.content
      studentData[columnDatum.user_id] = dataForStudent

    fetchColumnData: (col, url) ->
      url ||= ENV.GRADEBOOK_OPTIONS.custom_column_data_url.replace /:id/, col.id
      ajax.raw(url, {dataType:"json"}).then (result) =>
        for datum in result.response
          @updateColumnData datum, col.id
        meta = parseLinkHeader result.jqXHR
        if meta.next
          @fetchColumnData col, meta.next
        else
          setProperties col,
            'isLoading': false
            'isLoaded': true

    dataForStudent: (->
      selectedStudent = @get('selectedStudent')
      return unless selectedStudent?
      @get('studentColumnData')[selectedStudent.id]
    ).property('selectedStudent', 'custom_columns.@each.isLoaded')

    loadCustomColumnData: (->
      return unless (@get('enrollments.isLoaded'))
      @get('custom_columns').filter((col) ->
        return false if get(col, 'isLoaded') or get(col, 'isLoading')
        set col, 'isLoading', true
        col
      ).forEach (col) =>
        @fetchColumnData col
    ).observes('enrollments.isLoaded', 'custom_columns.@each')

    studentsInSelectedSection: (->
      students = @get('students')
      currentSection = @get('selectedSection')

      return students if not currentSection
      students.filter (s) -> s.sections.contains(currentSection.id)
    ).property('students.@each', 'selectedSection')

    groupById: (array) ->
      array.reduce( (obj, item) ->
        obj[get(item, 'id')] = item
        obj
      ,{})

    submissionsLoaded: (->
      assignments = @get("assignmentsFromGroups")
      assignmentsByID = @groupById assignments
      studentsByID = @groupById @get("students")
      submissions = @get('submissions')
      submissions.forEach ((submission) ->
{'fraction_non_alphanumeric': 0.11253817681681196, 'fraction_numerical': 0.002224874092352501, 'mean_word_length': 3.371143135001326, 'pattern_counts': {'":': 8, '<': 7, '<?xml version=': 0, '>': 197, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1225132', 'n_tokens_mistral': 14886, 'n_tokens_neox': 14320, 'n_words': 2486}