• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 23774787472

31 Mar 2026 12:39AM UTC coverage: 91.726% (+0.01%) from 91.713%
23774787472

Pull #7886

github

web-flow
Merge 7a88251dd into 166782a29
Pull Request #7886: TICKET-604: Return structured JSON from grade entry forms show endpoint

948 of 1842 branches covered (51.47%)

Branch coverage included in aggregate %.

107 of 109 new or added lines in 2 files covered. (98.17%)

23 existing lines in 2 files now uncovered.

45258 of 48532 relevant lines covered (93.25%)

130.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.17
/app/controllers/api/grade_entry_forms_controller.rb
1
module Api
1✔
2
  class GradeEntryFormsController < MainApiController
1✔
3
    DEFAULT_FIELDS = [:id, :short_identifier, :description, :due_date, :is_hidden, :visible_on, :visible_until,
1✔
4
                      :show_total].freeze
5

6
    # Returns the specified grade entry form with all grade data
7
    # Requires: id
8
    # Optional: user_name (filter to a single student), download=csv (export as CSV)
9
    def show
1✔
10
      grade_entry_form = record
8✔
11
      if grade_entry_form.nil?
8✔
NEW
UNCOV
12
        render 'shared/http_status', locals: { code: '404', message:
×
13
          'Grade Entry Form not found' }, status: :not_found
NEW
14
        return
×
15
      end
16

17
      if params[:download] == 'csv'
8✔
18
        send_data grade_entry_form.export_as_csv(current_role),
1✔
19
                  type: 'text/csv',
20
                  filename: "#{grade_entry_form.short_identifier}_grades_report.csv",
21
                  disposition: 'inline'
22
        return
1✔
23
      end
24

25
      grade_entry_items = grade_entry_form.grade_entry_items.order(:position)
7✔
26

27
      students_query = Student.left_outer_joins(:grade_entry_students, :user, :section)
7✔
28
                              .where(hidden: false, 'grade_entry_students.assessment_id': grade_entry_form.id)
29
      students_query = students_query.where('users.user_name': params[:user_name]) if params[:user_name].present?
7✔
30
      students = students_query.order(:user_name)
7✔
31
                               .pluck_to_hash(:user_name, :last_name, :first_name, 'sections.name as section_name',
32
                                              :id_number, :email, 'grade_entry_students.id as ges_id')
33

34
      if params[:user_name].present? && students.empty?
7✔
35
        render 'shared/http_status', locals: { code: '422', message:
1✔
36
          'No student with that user_name' }, status: :unprocessable_content
37
        return
1✔
38
      end
39

40
      grades_query = grade_entry_form.grades
6✔
41
                                     .joins(:grade_entry_item, grade_entry_student: :user)
42
      grades_query = grades_query.where('users.user_name': params[:user_name]) if params[:user_name].present?
6✔
43
      grade_data = grades_query.pluck('users.user_name', 'grade_entry_items.name', :grade)
6✔
44
                               .group_by { |g| g[0] }
5✔
45

46
      total_grades = GradeEntryStudent.get_total_grades(students.pluck(:ges_id))
6✔
47

48
      student_data = students.map do |student|
6✔
49
        grades_hash = {}
5✔
50
        grade_data[student[:user_name]]&.each { |g| grades_hash[g[1]] = g[2] }
10✔
51
        entry = {
52
          user_name: student[:user_name],
5✔
53
          first_name: student[:first_name],
54
          last_name: student[:last_name],
55
          id_number: student[:id_number],
56
          email: student[:email],
57
          section_name: student[:section_name],
58
          grades: grades_hash
59
        }
60
        entry[:total_grade] = total_grades[student[:ges_id]] if grade_entry_form.show_total
5✔
61
        entry
5✔
62
      end
63

64
      form_data = grade_entry_form.as_json(only: DEFAULT_FIELDS)
6✔
65
      form_data['grade_entry_items'] = grade_entry_items.as_json(only: [:id, :name, :out_of, :bonus])
6✔
66
      form_data['students'] = student_data
6✔
67

68
      respond_to do |format|
6✔
69
        format.xml { render xml: form_data.to_xml(root: 'grade_entry_form', skip_types: 'true') }
7✔
70
        format.json { render json: form_data }
11✔
71
      end
72
    end
73

74
    def index
1✔
75
      grade_entry_forms = get_collection(current_course.grade_entry_forms) || return
18✔
76

77
      include_args = { only: DEFAULT_FIELDS, include: { grade_entry_items: { only: [:id, :name, :out_of] } } }
18✔
78

79
      respond_to do |format|
18✔
80
        format.xml do
18✔
81
          xml = grade_entry_forms.to_xml(**include_args, root: 'grade_entry_forms', skip_types: 'true')
9✔
82
          render xml: xml
9✔
83
        end
84
        format.json { render json: grade_entry_forms.to_json(include_args) }
27✔
85
      end
86
    end
87

88
    # create a new grade entry form
89
    # Requires:
90
    #   :short_identifier, :description
91
    # Optional:
92
    #   :description, :due_date, :is_hidden
93
    #   grade_entry_items:
94
    #     :name, :out_of, :bonus
95
    def create
1✔
96
      if has_missing_params?([:short_identifier])
10✔
97
        # incomplete/invalid HTTP params
98
        render 'shared/http_status', locals: { code: '422', message:
2✔
99
          HttpStatusHelper::ERROR_CODE['message']['422'] }, status: :unprocessable_content
100
        return
2✔
101
      end
102

103
      # check if there is an existing assignment
104
      form = current_course.grade_entry_forms.find_by(short_identifier: params[:short_identifier])
8✔
105
      unless form.nil?
8✔
106
        render 'shared/http_status', locals: { code: '409', message:
1✔
107
          'Grade Entry Form already exists' }, status: :conflict
108
        return
1✔
109
      end
110

111
      ApplicationRecord.transaction do
7✔
112
        create_params = params.permit(*DEFAULT_FIELDS)
7✔
113
        create_params[:is_hidden] ||= false
7✔
114
        create_params[:description] ||= ''
7✔
115
        create_params[:course_id] = params[:course_id]
7✔
116
        new_form = GradeEntryForm.new(create_params)
7✔
117
        unless new_form.save
7✔
118
          render 'shared/http_status', locals: { code: '422', message:
3✔
119
            new_form.errors.full_messages.first }, status: :unprocessable_content
120
          raise ActiveRecord::Rollback
3✔
121
        end
122

123
        params[:grade_entry_items]&.each&.with_index do |column_params, i|
4✔
124
          column_params = column_params.permit(:name, :out_of, :bonus).to_h.symbolize_keys
×
125
          grade_item = new_form.grade_entry_items.build(**column_params, position: i + 1)
×
UNCOV
126
          unless grade_item.save
×
127
            render 'shared/http_status', locals: { code: '422', message:
×
128
              grade_item.errors.full_messages.first }, status: :unprocessable_content
UNCOV
129
            raise ActiveRecord::Rollback
×
130
          end
131
        end
132
        render 'shared/http_status', locals: { code: '201', message:
4✔
133
          HttpStatusHelper::ERROR_CODE['message']['201'] }, status: :created
134
      end
135
    end
136

137
    # create a new grade entry form
138
    # params:
139
    #   :short_identifier, :description, :date, :is_hidden
140
    #   grade_entry_items:
141
    #     :id, :name, :out_of, :bonus
142
    #
143
    # if the grade_entry_items id param is set, an existing item will be
144
    # updated, otherwise a new grade_entry_item will be created
145
    def update
1✔
146
      # check if there is an existing assignment
147
      form = record
2✔
148
      if form.nil?
2✔
UNCOV
149
        render 'shared/http_status', locals: { code: '404', message:
×
150
          'Grade Entry Form not found' }, status: :not_found
UNCOV
151
        return
×
152
      end
153

154
      ApplicationRecord.transaction do
2✔
155
        update_params = params.permit(*DEFAULT_FIELDS)
2✔
156
        unless form.update(update_params)
2✔
157
          render 'shared/http_status', locals: { code: '500', message:
1✔
158
            form.errors.full_messages.first }, status: :internal_server_error
159
          raise ActiveRecord::Rollback
1✔
160
        end
161

162
        position = form.grade_entry_items.count
1✔
163
        params[:grade_entry_items]&.each do |column_params|
1✔
UNCOV
164
          if column_params[:id].nil?
×
UNCOV
165
            column_params = column_params.permit(:name, :out_of, :bonus).to_h.symbolize_keys
×
166
            grade_item = form.grade_entry_items.build(**column_params, position: position += 1)
×
UNCOV
167
            unless grade_item.save
×
168
              render 'shared/http_status', locals: { code: '500', message:
×
169
                grade_item.errors.full_messages.first }, status: :internal_server_error
UNCOV
170
              raise ActiveRecord::Rollback
×
171
            end
172
          else
UNCOV
173
            column_params = column_params.permit(:id, :name, :out_of, :bonus).to_h.symbolize_keys
×
UNCOV
174
            grade_item = form.grade_entry_items.where(id: column_params[:id]).first
×
UNCOV
175
            if grade_item.nil?
×
UNCOV
176
              render 'shared/http_status', locals: { code: '404', message:
×
177
                "Grade Entry Item with id=#{column_params[:id]} not found" }, status: :not_found
UNCOV
178
              raise ActiveRecord::Rollback
×
179
            end
UNCOV
180
            unless grade_item.update(column_params)
×
UNCOV
181
              render 'shared/http_status', locals: { code: '500', message:
×
182
                grade_item.errors.full_messages.first }, status: :internal_server_error
UNCOV
183
              raise ActiveRecord::Rollback
×
184
            end
185
          end
186
        end
187
        render 'shared/http_status', locals: { code: '200', message:
1✔
188
          HttpStatusHelper::ERROR_CODE['message']['200'] }, status: :ok
189
      end
190
    end
191

192
    def update_grades
1✔
193
      if has_missing_params?([:user_name, :grade_entry_items])
3✔
194
        # incomplete/invalid HTTP params
UNCOV
195
        render 'shared/http_status', locals: { code: '422', message:
×
196
          HttpStatusHelper::ERROR_CODE['message']['422'] }, status: :unprocessable_content
UNCOV
197
        return
×
198
      end
199

200
      grade_entry_form = record
3✔
201

202
      grade_entry_student = grade_entry_form.grade_entry_students
3✔
203
                                            .joins(:user)
204
                                            .where('users.user_name': params[:user_name])
205
                                            .first
206

207
      if grade_entry_student.nil?
3✔
208
        # There is no student with that user_name
UNCOV
209
        render 'shared/http_status', locals: { code: '422', message:
×
210
          'There is no student with that user_name' }, status: :unprocessable_content
UNCOV
211
        return
×
212
      end
213

214
      Grade.transaction do
3✔
215
        params[:grade_entry_items].each do |item, score|
3✔
216
          grade_entry_item = GradeEntryItem.find_by(name: item, assessment_id: params[:id])
3✔
217

218
          if grade_entry_item.nil?
3✔
219
            # There is no such grade entry item
220
            render 'shared/http_status', locals: { code: '422', message:
×
221
              "There is no grade entry item named #{item}" }, status: :unprocessable_content
222
            raise ActiveRecord::Rollback
×
223
          end
224
          grade = grade_entry_student.grades.find_or_create_by(grade_entry_item_id: grade_entry_item.id)
3✔
225
          grade.update(grade: score)
3✔
226
        end
227
        if grade_entry_student.save
3✔
228
          render 'shared/http_status', locals: { code: '200', message:
3✔
229
            HttpStatusHelper::ERROR_CODE['message']['200'] }, status: :ok
230
        else
231
          # Some error occurred (including invalid mark)
232
          render 'shared/http_status', locals: { code: '500', message:
×
233
            grade_entry_student.errors.full_messages.first }, status: :internal_server_error
234
          raise ActiveRecord::Rollback
×
235
        end
236
      end
237
    end
238

239
    def destroy
1✔
240
      # check if the grade entry form exists
241
      grade_entry_form = record
2✔
242
      if grade_entry_form.nil?
2✔
243
        render 'shared/http_status', locals: { code: '404', message:
×
244
          'Grade Entry Form not found' }, status: :not_found
245
        return
×
246
      end
247
      # delete the grade entry form
248
      begin
249
        grade_entry_form.destroy!
2✔
250
        render 'shared/http_status',
1✔
251
               locals: { code: '200',
252
                         message: 'Grade Entry Form successfully deleted' }, status: :ok
253
      rescue ActiveRecord::RecordNotDestroyed
254
        render 'shared/http_status',
1✔
255
               locals: { code: :conflict,
256
                         message: 'Grade Entry Form contains non-nil grades' }, status: :conflict
257
      end
258
    end
259
  end
260
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc