-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] [MER-3790] core schema for certificates (#5297)
This PR adds the certificate and granted_certificate models, along with their migrations and Ecto schema. There is additional work required for MER-3790, but this PR enables progress on other tickets dependent on the database models.
- Loading branch information
1 parent
0ed8488
commit 88650d4
Showing
8 changed files
with
461 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
defmodule Oli.Delivery.Sections.Certificate do | ||
use Ecto.Schema | ||
import Ecto.Changeset | ||
import Oli.Utils, only: [validate_greater_than_or_equal: 4] | ||
|
||
alias Oli.Delivery.Sections.Section | ||
|
||
@assessments_options [:all, :custom] | ||
|
||
schema "certificates" do | ||
field :required_discussion_posts, :integer | ||
field :required_class_notes, :integer | ||
field :min_percentage_for_completion, :float | ||
field :min_percentage_for_distinction, :float | ||
field :assessments_apply_to, Ecto.Enum, values: @assessments_options, default: :all | ||
field :custom_assessments, {:array, :integer}, default: [] | ||
field :requires_instructor_approval, :boolean, default: false | ||
|
||
field :title, :string | ||
field :description, :string | ||
|
||
field :admin_name1, :string | ||
field :admin_title1, :string | ||
field :admin_name2, :string | ||
field :admin_title2, :string | ||
field :admin_name3, :string | ||
field :admin_title3, :string | ||
|
||
field :logo1, :string | ||
field :logo2, :string | ||
field :logo3, :string | ||
|
||
belongs_to :section, Section | ||
|
||
timestamps(type: :utc_datetime) | ||
end | ||
|
||
@required_fields [ | ||
:required_discussion_posts, | ||
:required_class_notes, | ||
:min_percentage_for_completion, | ||
:min_percentage_for_distinction, | ||
:assessments_apply_to, | ||
:title, | ||
:description, | ||
:section_id | ||
] | ||
|
||
@optional_fields [ | ||
:custom_assessments, | ||
:requires_instructor_approval, | ||
:admin_name1, | ||
:admin_title1, | ||
:admin_name2, | ||
:admin_title2, | ||
:admin_name3, | ||
:admin_title3, | ||
:logo1, | ||
:logo2, | ||
:logo3 | ||
] | ||
|
||
@all_fields @required_fields ++ @optional_fields | ||
|
||
def changeset(params \\ %{}) do | ||
changeset(%__MODULE__{}, params) | ||
end | ||
|
||
def changeset(certificate, params) do | ||
certificate | ||
|> cast(params, @all_fields) | ||
|> validate_required(@required_fields) | ||
|> validate_greater_than_or_equal( | ||
:min_percentage_for_completion, | ||
:min_percentage_for_distinction, | ||
allow_equal: true | ||
) | ||
|> assoc_constraint(:section) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
defmodule Oli.Delivery.Sections.GrantedCertificate do | ||
use Ecto.Schema | ||
import Ecto.Changeset | ||
|
||
alias Oli.Accounts.User | ||
alias Oli.Delivery.Sections.Certificate | ||
|
||
@state_enum [:pending, :earned, :denied] | ||
@issued_by_type_enum [:user, :author] | ||
|
||
schema "granted_certificates" do | ||
field :state, Ecto.Enum, values: @state_enum | ||
field :with_distinction, :boolean | ||
field :guid, :string | ||
field :issued_by, :integer | ||
field :issued_by_type, Ecto.Enum, values: @issued_by_type_enum, default: :user | ||
field :issued_at, :utc_datetime | ||
|
||
belongs_to :certificate, Certificate | ||
belongs_to :user, User | ||
|
||
timestamps(type: :utc_datetime) | ||
end | ||
|
||
@required_fields [:user_id, :certificate_id, :state, :with_distinction, :guid] | ||
@optional_fields [:issued_by, :issued_by_type, :issued_at] | ||
|
||
@all_fields @required_fields ++ @optional_fields | ||
|
||
def changeset(params \\ %{}) do | ||
changeset(%__MODULE__{}, params) | ||
end | ||
|
||
def changeset(granted_certificates, params) do | ||
granted_certificates | ||
|> cast(params, @all_fields) | ||
|> validate_required(@required_fields) | ||
|> assoc_constraint(:certificate) | ||
|> assoc_constraint(:user) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
priv/repo/migrations/20241203184246_create_certificates_tables.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
defmodule Oli.Repo.Migrations.CreateCertificatesTables do | ||
use Ecto.Migration | ||
|
||
def change do | ||
execute_types() | ||
|
||
create table(:certificates) do | ||
add :required_discussion_posts, :integer | ||
add :required_class_notes, :integer | ||
add :min_percentage_for_completion, :float | ||
add :min_percentage_for_distinction, :float | ||
add :assessments_apply_to, :certificates_assessments_apply_to, default: "all" | ||
add :custom_assessments, {:array, :integer}, default: [] | ||
add :requires_instructor_approval, :boolean, default: false | ||
|
||
add :title, :string | ||
add :description, :string | ||
|
||
add :admin_name1, :string | ||
add :admin_title1, :string | ||
add :admin_name2, :string | ||
add :admin_title2, :string | ||
add :admin_name3, :string | ||
add :admin_title3, :string | ||
|
||
add :logo1, :string | ||
add :logo2, :string | ||
add :logo3, :string | ||
|
||
add :section_id, references(:sections, on_delete: :delete_all), null: false | ||
|
||
timestamps(type: :utc_datetime) | ||
end | ||
|
||
create table(:granted_certificates) do | ||
add :state, :granted_certificates_state | ||
add :with_distinction, :boolean | ||
add :guid, :string | ||
add :issued_by, :integer, allow_nil: true | ||
add :issued_by_type, :granted_certificates_issued_by_type | ||
add :issued_at, :utc_datetime, allow_nil: true | ||
|
||
add :certificate_id, references(:certificates) | ||
add :user_id, references(:users) | ||
|
||
timestamps(type: :utc_datetime) | ||
end | ||
|
||
create unique_index(:granted_certificates, [:user_id, :certificate_id], | ||
name: :unique_user_certificate | ||
) | ||
|
||
alter table(:sections) do | ||
add :certificate_enabled, :boolean, default: false | ||
end | ||
end | ||
|
||
defp execute_types() do | ||
execute( | ||
"CREATE TYPE public.certificates_assessments_apply_to AS ENUM ('all', 'custom');", | ||
"DROP TYPE public.certificates_assessments_apply_to;" | ||
) | ||
|
||
execute( | ||
"CREATE TYPE public.granted_certificates_issued_by_type AS ENUM ('user', 'autor');", | ||
"DROP TYPE public.granted_certificates_issued_by_type;" | ||
) | ||
|
||
execute( | ||
"CREATE TYPE public.granted_certificates_state AS ENUM ('pending', 'earned', 'denied');", | ||
"DROP TYPE public.granted_certificates_state;" | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
defmodule Oli.Delivery.Sections.CertificateTest do | ||
use Oli.DataCase, async: true | ||
|
||
alias Oli.Delivery.Sections.Certificate | ||
|
||
describe "changeset/2" do | ||
setup [:valid_params] | ||
|
||
test "success: with valid params", %{params: params} do | ||
changeset = %Ecto.Changeset{} = Certificate.changeset(params) | ||
|
||
assert changeset.valid? | ||
end | ||
|
||
test "check: assessments_apply_to default value", %{params: params} do | ||
params = Map.delete(params, :assessments_apply_to) | ||
refute Map.has_key?(params, :assessments_apply_to) | ||
|
||
certificate = Certificate.changeset(params) |> apply_changes() | ||
|
||
assert certificate.assessments_apply_to == :all | ||
end | ||
|
||
test "check: custom_assessments default value", %{params: params} do | ||
params = Map.delete(params, :custom_assessments) | ||
refute Map.has_key?(params, :custom_assessments) | ||
|
||
certificate = Certificate.changeset(params) |> apply_changes() | ||
|
||
assert certificate.custom_assessments == [] | ||
end | ||
|
||
test "check: requires_instructor_approval default value", %{params: params} do | ||
params = Map.delete(params, :requires_instructor_approval) | ||
refute Map.has_key?(params, :requires_instructor_approval) | ||
|
||
certificate = Certificate.changeset(params) |> apply_changes() | ||
|
||
assert certificate.requires_instructor_approval == false | ||
end | ||
|
||
test "error: fails when doesn't have the required fields" do | ||
params = %{} | ||
|
||
changeset = %Ecto.Changeset{errors: errors} = Certificate.changeset(params) | ||
|
||
refute changeset.valid? | ||
assert length(errors) == 7 | ||
|
||
assert %{title: ["can't be blank"]} = errors_on(changeset) | ||
assert %{description: ["can't be blank"]} = errors_on(changeset) | ||
assert %{section_id: ["can't be blank"]} = errors_on(changeset) | ||
assert %{required_discussion_posts: ["can't be blank"]} = errors_on(changeset) | ||
assert %{required_class_notes: ["can't be blank"]} = errors_on(changeset) | ||
assert %{min_percentage_for_completion: ["can't be blank"]} = errors_on(changeset) | ||
assert %{min_percentage_for_distinction: ["can't be blank"]} = errors_on(changeset) | ||
end | ||
|
||
test "success: when distinction > completion", %{params: params} do | ||
params = %{params | min_percentage_for_distinction: 0.7, min_percentage_for_completion: 0.6} | ||
|
||
changeset = %Ecto.Changeset{} = Certificate.changeset(params) | ||
|
||
assert changeset.valid? | ||
end | ||
|
||
test "success: when distinction = completion", %{params: params} do | ||
params = %{params | min_percentage_for_distinction: 0.7, min_percentage_for_completion: 0.7} | ||
|
||
changeset = %Ecto.Changeset{} = Certificate.changeset(params) | ||
|
||
assert changeset.valid? | ||
end | ||
|
||
test "error: when distinction < completion", %{params: params} do | ||
params = %{params | min_percentage_for_distinction: 0.6, min_percentage_for_completion: 0.7} | ||
|
||
changeset = %Ecto.Changeset{} = Certificate.changeset(params) | ||
|
||
refute changeset.valid? | ||
|
||
assert %{ | ||
min_percentage_for_completion: [ | ||
"min_percentage_for_distinction must be greater than min_percentage_for_completion" | ||
] | ||
} = errors_on(changeset) | ||
end | ||
|
||
test "error: incorrect assessments_apply_to type definition", %{params: params} do | ||
params = %{params | assessments_apply_to: "incorrect_type"} | ||
|
||
changeset = %Ecto.Changeset{} = Certificate.changeset(params) | ||
|
||
refute changeset.valid? | ||
|
||
assert %{assessments_apply_to: ["is invalid"]} = errors_on(changeset) | ||
end | ||
end | ||
|
||
describe "insert" do | ||
setup [:valid_params] | ||
|
||
test "error: missing section association", %{params: params} do | ||
{:error, changeset = %Ecto.Changeset{}} = Certificate.changeset(params) |> Oli.Repo.insert() | ||
|
||
refute changeset.valid? | ||
|
||
assert %{section: ["does not exist"]} = errors_on(changeset) | ||
end | ||
end | ||
|
||
defp valid_params(_) do | ||
params = %{ | ||
required_discussion_posts: 11, | ||
min_percentage_for_completion: 0.6, | ||
min_percentage_for_distinction: 0.7, | ||
required_class_notes: 12, | ||
title: "My Certificate", | ||
description: "Some description", | ||
section_id: 123, | ||
assessments_apply_to: :all | ||
} | ||
|
||
%{params: params} | ||
end | ||
end |
Oops, something went wrong.