Skip to content

Commit

Permalink
Merge pull request #183 from camino-school/181-fix-entry-preview
Browse files Browse the repository at this point in the history
Fix entry preview and prevent manual grades overwriting when re-calculating
  • Loading branch information
endoooo authored Jun 20, 2024
2 parents 983f11e + ddaf3bd commit 426a4d8
Show file tree
Hide file tree
Showing 11 changed files with 649 additions and 201 deletions.
98 changes: 78 additions & 20 deletions lib/lanttern/grades_reports.ex
Original file line number Diff line number Diff line change
Expand Up @@ -521,22 +521,30 @@ defmodule Lanttern.GradesReports do
Uses a third elemente in the `:ok` returned tuple:
- `:created` when the `StudentGradeReportEntry` is created
- `:updated` when the `StudentGradeReportEntry` is updated
- `:updated_with_manual` when the `StudentGradeReportEntry` is updated, except from manually adjusted `ordinal_value_id` or `score`
- `:deleted` when the `StudentGradeReportEntry` is deleted (always `nil` in the second element)
- `:noop` when the nothing is created, updated, or deleted (always `nil` in the second element)
### Options
- `:force_overwrite` - ignore the update with manual rule, and overwrite grade if needed
"""
@spec calculate_student_grade(
student_id :: integer(),
grades_report_id :: integer(),
grades_report_cycle_id :: integer(),
grades_report_subject_id :: integer()
grades_report_subject_id :: integer(),
Keyword.t()
) ::
{:ok, StudentGradeReportEntry.t() | nil, :created | :updated | :deleted | :noop}
{:ok, StudentGradeReportEntry.t() | nil,
:created | :updated | :updated_keep_manual | :deleted | :noop}
| {:error, Ecto.Changeset.t()}
def calculate_student_grade(
student_id,
grades_report_id,
grades_report_cycle_id,
grades_report_subject_id
grades_report_subject_id,
opts \\ []
) do
# get grades report scale
%{scale: scale} = get_grades_report!(grades_report_id, preloads: :scale)
Expand Down Expand Up @@ -569,17 +577,29 @@ defmodule Lanttern.GradesReports do
grades_report_id,
grades_report_cycle_id,
grades_report_subject_id,
scale
scale,
opts
)
end

defp handle_student_grades_report_entry_creation(
entries_and_grade_components,
student_id,
grades_report_id,
grades_report_cycle_id,
grades_report_subject_id,
scale,
opts \\ []
)

defp handle_student_grades_report_entry_creation(
[],
student_id,
_grades_report_id,
grades_report_cycle_id,
grades_report_subject_id,
_scale
_scale,
_opts
) do
# delete existing student grade report entry if needed
Repo.get_by(StudentGradeReportEntry,
Expand All @@ -605,7 +625,8 @@ defmodule Lanttern.GradesReports do
grades_report_id,
grades_report_cycle_id,
grades_report_subject_id,
scale
scale,
opts
) do
{normalized_avg, composition} =
calculate_weighted_avg_and_build_comp_metadata(entries_and_grade_components)
Expand Down Expand Up @@ -637,24 +658,60 @@ defmodule Lanttern.GradesReports do
composition_datetime: DateTime.utc_now()
})

force_overwrite =
case Keyword.get(opts, :force_overwrite) do
true -> true
_ -> false
end

# create or update existing student grade report entry
Repo.get_by(StudentGradeReportEntry,
student_id: student_id,
grades_report_cycle_id: grades_report_cycle_id,
grades_report_subject_id: grades_report_subject_id
)
|> case do
nil ->
case create_student_grade_report_entry(attrs) do
{:ok, sgre} -> {:ok, sgre, :created}
error_tuple -> error_tuple
end
|> create_or_update_student_grade_report_entry(attrs, force_overwrite)
end

sgre ->
case update_student_grade_report_entry(sgre, attrs) do
{:ok, sgre} -> {:ok, sgre, :updated}
error_tuple -> error_tuple
end
defp create_or_update_student_grade_report_entry(nil, attrs, _) do
case create_student_grade_report_entry(attrs) do
{:ok, sgre} -> {:ok, sgre, :created}
error_tuple -> error_tuple
end
end

defp create_or_update_student_grade_report_entry(
%{ordinal_value_id: ov_id, composition_ordinal_value_id: comp_ov_id} = sgre,
attrs,
false
)
when ov_id != comp_ov_id do
attrs = Map.drop(attrs, [:ordinal_value_id])

case update_student_grade_report_entry(sgre, attrs) do
{:ok, sgre} -> {:ok, sgre, :updated_with_manual}
error_tuple -> error_tuple
end
end

defp create_or_update_student_grade_report_entry(
%{score: score, composition_score: comp_score} = sgre,
attrs,
false
)
when score != comp_score do
attrs = Map.drop(attrs, [:score])

case update_student_grade_report_entry(sgre, attrs) do
{:ok, sgre} -> {:ok, sgre, :updated_with_manual}
error_tuple -> error_tuple
end
end

defp create_or_update_student_grade_report_entry(sgre, attrs, _force_overwrite) do
case update_student_grade_report_entry(sgre, attrs) do
{:ok, sgre} -> {:ok, sgre, :updated}
error_tuple -> error_tuple
end
end

Expand Down Expand Up @@ -724,6 +781,7 @@ defmodule Lanttern.GradesReports do
@type batch_calculation_results() :: %{
created: integer(),
updated: integer(),
updated_with_manual: integer(),
deleted: integer(),
noop: integer()
}
Expand Down Expand Up @@ -800,7 +858,7 @@ defmodule Lanttern.GradesReports do
grades_report_id,
grades_report_cycle_id,
scale,
results \\ %{created: 0, updated: 0, deleted: 0, noop: 0}
results \\ %{created: 0, updated: 0, updated_with_manual: 0, deleted: 0, noop: 0}
)

defp handle_grades_report_subject_entries_and_grade_components(
Expand Down Expand Up @@ -922,7 +980,7 @@ defmodule Lanttern.GradesReports do
grades_report_cycle_id,
grades_report_subject_id,
scale,
results \\ %{created: 0, updated: 0, deleted: 0, noop: 0}
results \\ %{created: 0, updated: 0, updated_with_manual: 0, deleted: 0, noop: 0}
)

defp handle_students_entries_and_grade_components(
Expand Down Expand Up @@ -1051,7 +1109,7 @@ defmodule Lanttern.GradesReports do
grades_report_id,
grades_report_cycle_id,
scale,
results \\ %{created: 0, updated: 0, deleted: 0, noop: 0}
results \\ %{created: 0, updated: 0, updated_with_manual: 0, deleted: 0, noop: 0}
)

defp handle_students_grades_report_subjects_entries_and_grade_components(
Expand Down
25 changes: 25 additions & 0 deletions lib/lanttern_web/components/grades_reports_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,27 @@ defmodule LantternWeb.GradesReportsComponents do
else: "border-ltrn-teacher-accent bg-ltrn-teacher-lightest"
end

has_manual_grade =
case assigns.student_grade_report_entry do
%{
ordinal_value_id: ov_id,
composition_ordinal_value_id: comp_ov_id
}
when ov_id != comp_ov_id ->
true

%{score: score, composition_score: comp_score}
when score != comp_score ->
true

_ ->
false
end

assigns =
assigns
|> assign(:bg_class, bg_class)
|> assign(:has_manual_grade, has_manual_grade)

~H"""
<div class={[
Expand Down Expand Up @@ -484,6 +502,13 @@ defmodule LantternWeb.GradesReportsComponents do
size="sm"
sr_text={gettext("Recalculate grade")}
phx-click={@on_calculate_cell.(@student_id, @grades_report_subject_id)}
data-confirm={
if @has_manual_grade,
do:
gettext(
"There is a manual grade change that will be overwritten by this operation. Are you sure you want to proceed?"
)
}
/>
</div>
</div>
Expand Down
14 changes: 10 additions & 4 deletions lib/lanttern_web/components/reporting_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule LantternWeb.ReportingComponents do
import Lanttern.SupabaseHelpers, only: [object_url_to_render_url: 2]

alias Lanttern.Assessments.AssessmentPointEntry
alias Lanttern.Grading.OrdinalValue
alias Lanttern.Grading.Scale
alias Lanttern.Reporting.ReportCard
alias Lanttern.Rubrics.Rubric
Expand Down Expand Up @@ -219,27 +220,32 @@ defmodule LantternWeb.ReportingComponents do
required: true,
doc: "Requires `scale` and `ordinal_value` preloads"

# attr :scale, Scale, required: true, doc: "Requires `ordinal_values` preload"
# attr :rubric, Rubric, default: nil, doc: "Requires `descriptors` preload"
attr :id, :string, default: nil
attr :class, :any, default: nil

def assessment_point_entry_preview(%{entry: %{scale: %{type: "ordinal"}}} = assigns) do
def assessment_point_entry_preview(
%{entry: %{ordinal_value: %OrdinalValue{}, scale: %{type: "ordinal"}}} = assigns
) do
~H"""
<.ordinal_value_badge ordinal_value={@entry.ordinal_value} class={@class} id={@id}>
<%= String.slice(@entry.ordinal_value.name, 0..2) %>
</.ordinal_value_badge>
"""
end

def assessment_point_entry_preview(%{entry: %{scale: %{type: "numeric"}}} = assigns) do
def assessment_point_entry_preview(
%{entry: %{score: score, scale: %{type: "numeric"}}} = assigns
)
when not is_nil(score) do
~H"""
<.badge class={@class} id={@id}>
<%= @entry.score %>
</.badge>
"""
end

def assessment_point_entry_preview(_assigns), do: nil

attr :footnote, :string, required: true
attr :class, :any, default: nil

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ defmodule LantternWeb.ReportCardLive.StudentsGradesComponent do
student_id,
socket.assigns.grades_report.id,
socket.assigns.current_grades_report_cycle.id,
grades_report_subject_id
grades_report_subject_id,
force_overwrite: true
)
|> case do
{:ok, nil, _} ->
Expand Down Expand Up @@ -427,6 +428,17 @@ defmodule LantternWeb.ReportCardLive.StudentsGradesComponent do
build_calculation_results_message(results, [msg | msgs])
end

defp build_calculation_results_message([{:updated_with_manual, count} | results], msgs) do
msg =
ngettext(
"1 grade partially updated (only composition, manual grade not changed)",
"%{count} grades partially updated (only compositions, manual grades not changed)",
count
)

build_calculation_results_message(results, [msg | msgs])
end

defp build_calculation_results_message([{:deleted, count} | results], msgs) do
msg = ngettext("1 grade removed", "%{count} grades removed", count)
build_calculation_results_message(results, [msg | msgs])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
>
<:bottom_content :if={entries != []}>
<div class="flex flex-wrap gap-2 p-6 border-t border-ltrn-lighter">
<.assessment_point_entry_preview :for={entry <- entries} entry={entry} />
<%= for entry <- entries, entry.ordinal_value || entry.score do %>
<.assessment_point_entry_preview entry={entry} />
<% end %>
</div>
</:bottom_content>
</.strand_card>
Expand Down
Loading

0 comments on commit 426a4d8

Please sign in to comment.