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

source-academy / backend / 18dc689a4df4836fc6967bf0f74dc252964bd175-PR-1180

08 Sep 2024 06:14PM UTC coverage: 79.088% (-15.3%) from 94.372%
18dc689a4df4836fc6967bf0f74dc252964bd175-PR-1180

Pull #1180

github

josh1248
Change appropriate routes into admin scope
Pull Request #1180: Transfer groundControl (and admin panel) from staff to admin route

7 of 12 new or added lines in 1 file covered. (58.33%)

499 existing lines in 25 files now uncovered.

2602 of 3290 relevant lines covered (79.09%)

1023.2 hits per line

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

91.84
/lib/cadet_web/helpers/assessments_helpers.ex
1
defmodule CadetWeb.AssessmentsHelpers do
2
  @moduledoc """
3
  Helper functions for Assessments and Grading
4
  """
5
  import CadetWeb.ViewHelper
6

7
  defp build_library(%{library: library}) do
8
    transform_map_for_view(library, %{
801✔
9
      chapter: :chapter,
10
      variant: :variant,
11
      execTimeMs: :exec_time_ms,
12
      globals: :globals,
13
      external: &build_external_library(%{external_library: &1.external})
801✔
14
    })
15
  end
16

17
  defp build_external_library(%{external_library: external_library}) do
18
    transform_map_for_view(external_library, [:name, :symbols])
801✔
19
  end
20

21
  def build_question_by_question_config(
22
        %{question: question},
23
        all_testcases? \\ false
801✔
24
      ) do
25
    Map.merge(
801✔
26
      build_generic_question_fields(%{question: question}),
27
      build_question_content_by_config(
28
        %{question: question},
29
        all_testcases?
30
      )
31
    )
32
  end
33

34
  def build_question_with_answer_and_solution_if_ungraded(%{question: question}) do
35
    components = [
801✔
36
      build_question_by_question_config(%{
37
        question: question
38
      }),
39
      build_answer_fields_by_question_type(%{question: question}),
40
      build_solution_if_ungraded_by_config(%{question: question})
41
    ]
42

43
    components
44
    |> Enum.filter(& &1)
45
    |> Enum.reduce(%{}, &Map.merge/2)
801✔
46
  end
47

48
  defp build_generic_question_fields(%{question: question}) do
49
    transform_map_for_view(question, %{
801✔
50
      id: :id,
51
      type: :type,
52
      library: &build_library(%{library: &1.library}),
801✔
53
      maxXp: :max_xp,
54
      blocking: :blocking
55
    })
56
  end
57

58
  defp build_solution_if_ungraded_by_config(%{
59
         question: %{question: question, type: question_type, show_solution: show_solution}
60
       }) do
61
    if show_solution do
801✔
62
      solution_getter =
135✔
63
        case question_type do
64
          :programming -> &Map.get(&1, "solution")
45✔
65
          :mcq -> &find_correct_choice(&1["choices"])
45✔
66
          :voting -> nil
45✔
67
        end
68

69
      transform_map_for_view(question, %{solution: solution_getter})
135✔
70
    end
71
  end
72

73
  defp answer_builder_for(:programming), do: & &1.answer["code"]
267✔
74
  defp answer_builder_for(:mcq), do: & &1.answer["choice_id"]
267✔
75
  defp answer_builder_for(:voting), do: nil
267✔
76

77
  defp build_answer_fields_by_question_type(%{
78
         question: %{answer: answer, type: question_type}
79
       }) do
80
    # No need to check if answer exists since empty answer would be a
81
    # `%Answer{..., answer: nil}` and nil["anything"] = nil
82

83
    %{grader: grader} = answer
801✔
84

85
    transform_map_for_view(answer, %{
801✔
86
      answer: answer_builder_for(question_type),
87
      lastModifiedAt: :last_modified_at,
88
      grader: grader_builder(grader),
89
      gradedAt: graded_at_builder(grader),
90
      xp: &((&1.xp || 0) + (&1.xp_adjustment || 0)),
801✔
91
      autogradingStatus: :autograding_status,
92
      autogradingResults: :autograding_results,
93
      comments: :comments
94
    })
95
  end
96

97
  defp build_contest_entry(entry) do
98
    transform_map_for_view(entry, %{
135✔
99
      submission_id: :submission_id,
100
      answer: :answer,
101
      score: :score
102
    })
103
  end
104

105
  def build_contest_leaderboard_entry(leaderboard_ans) do
106
    Map.put(
75✔
107
      transform_map_for_view(leaderboard_ans, %{
108
        submission_id: :submission_id,
109
        answer: :answer,
110
        student_name: :student_name
111
      }),
112
      "final_score",
113
      Float.round(leaderboard_ans.relative_score, 2)
75✔
114
    )
115
  end
116

117
  def build_popular_leaderboard_entry(leaderboard_ans) do
118
    Map.put(
75✔
119
      transform_map_for_view(leaderboard_ans, %{
120
        submission_id: :submission_id,
121
        answer: :answer,
122
        student_name: :student_name
123
      }),
124
      "final_score",
125
      Float.round(leaderboard_ans.popular_score, 2)
75✔
126
    )
127
  end
128

129
  defp build_choice(choice) do
130
    transform_map_for_view(choice, %{
801✔
131
      id: "choice_id",
132
      content: "content",
133
      hint: "hint"
134
    })
135
  end
136

137
  defp build_testcase(testcase, type) do
138
    transform_map_for_view(testcase, %{
534✔
139
      answer: "answer",
140
      score: "score",
141
      program: "program",
142
      # Create a 1-arity function to return the type of the testcase as a string
143
      type: fn _ -> type end
534✔
144
    })
145
  end
146

147
  defp build_testcases(all_testcases?) do
148
    if all_testcases? do
267✔
UNCOV
149
      &Enum.concat(
×
150
        Enum.concat(
UNCOV
151
          Enum.map(&1["public"], fn testcase -> build_testcase(testcase, "public") end),
×
UNCOV
152
          Enum.map(&1["opaque"], fn testcase -> build_testcase(testcase, "opaque") end)
×
153
        ),
UNCOV
154
        Enum.map(&1["secret"], fn testcase -> build_testcase(testcase, "secret") end)
×
155
      )
156
    else
157
      &Enum.concat(
267✔
158
        Enum.map(&1["public"], fn testcase -> build_testcase(testcase, "public") end),
267✔
159
        Enum.map(&1["opaque"], fn testcase -> build_testcase(testcase, "opaque") end)
267✔
160
      )
161
    end
162
  end
163

164
  defp build_question_content_by_config(
165
         %{
166
           question: %{
167
             question: question,
168
             type: question_type
169
           }
170
         },
171
         all_testcases?
172
       ) do
173
    case question_type do
801✔
174
      :programming ->
175
        transform_map_for_view(question, %{
267✔
176
          content: "content",
177
          prepend: "prepend",
178
          solutionTemplate: "template",
179
          postpend: "postpend",
180
          testcases: build_testcases(all_testcases?)
181
        })
182

183
      :mcq ->
184
        transform_map_for_view(question, %{
267✔
185
          content: "content",
186
          choices: &Enum.map(&1["choices"], fn choice -> build_choice(choice) end)
267✔
187
        })
188

189
      :voting ->
190
        transform_map_for_view(question, %{
267✔
191
          content: "content",
192
          prepend: "prepend",
193
          solutionTemplate: "template",
194
          contestEntries:
195
            &Enum.map(&1[:contest_entries], fn entry -> build_contest_entry(entry) end),
267✔
196
          scoreLeaderboard:
197
            &Enum.map(&1[:contest_leaderboard], fn entry ->
267✔
198
              build_contest_leaderboard_entry(entry)
75✔
199
            end),
200
          popularVoteLeaderboard:
201
            &Enum.map(&1[:popular_leaderboard], fn entry ->
267✔
202
              build_popular_leaderboard_entry(entry)
75✔
203
            end)
204
        })
205
    end
206
  end
207

208
  defp find_correct_choice(choices) do
209
    choices
210
    |> Enum.find(&Map.get(&1, "is_correct"))
45✔
211
    |> Map.get("choice_id")
45✔
212
  end
213
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

© 2025 Coveralls, Inc