From f53ca5d0920087a9a57dfcc6e8f159205994e028 Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Thu, 2 Oct 2025 16:55:54 +0200 Subject: [PATCH 1/7] feat: add new criteria and remove old criteria for pool adverts. --- .../gettext/de/LC_MESSAGES/link-advert.po | 8 + .../gettext/en/LC_MESSAGES/link-advert.po | 8 + .../gettext/es/LC_MESSAGES/link-advert.po | 8 + .../gettext/it/LC_MESSAGES/link-advert.po | 8 + core/priv/gettext/link-advert.pot | 8 + .../gettext/nl/LC_MESSAGES/link-advert.po | 8 + ...th_year_fields_to_eligibility_criteria.exs | 17 ++ core/systems/advert/advert_helpers.ex | 61 +++++++ core/systems/advert/selector_labels.ex | 34 ++++ core/systems/advert/submission_view.ex | 169 ++++++++---------- core/systems/citizen/_director.ex | 2 +- core/systems/pool/_public.ex | 23 ++- core/systems/pool/criteria_model.ex | 39 +++- core/systems/student/_director.ex | 2 +- 14 files changed, 293 insertions(+), 102 deletions(-) create mode 100644 core/priv/repo/migrations/20250918140818_add_birth_year_fields_to_eligibility_criteria.exs create mode 100644 core/systems/advert/advert_helpers.ex create mode 100644 core/systems/advert/selector_labels.ex diff --git a/core/priv/gettext/de/LC_MESSAGES/link-advert.po b/core/priv/gettext/de/LC_MESSAGES/link-advert.po index 4292b0829f..5952677cf4 100644 --- a/core/priv/gettext/de/LC_MESSAGES/link-advert.po +++ b/core/priv/gettext/de/LC_MESSAGES/link-advert.po @@ -94,3 +94,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "Hier findest du die aktuellsten Studien, die im panl-Pool veröffentlicht wurden. Klicke auf eine Studie, um weitere Details zu lesen." + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "Mindestgeburtsjahr" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "Höchstgeburtsjahr" diff --git a/core/priv/gettext/en/LC_MESSAGES/link-advert.po b/core/priv/gettext/en/LC_MESSAGES/link-advert.po index b4a42cf744..9e6ed1d50f 100644 --- a/core/priv/gettext/en/LC_MESSAGES/link-advert.po +++ b/core/priv/gettext/en/LC_MESSAGES/link-advert.po @@ -93,3 +93,11 @@ msgstr "Go to Settings" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "Here you can find the most recent studies published in the panl pool. Click on a study to read more details." + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "Minimum birth year" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "Maximum birth year" diff --git a/core/priv/gettext/es/LC_MESSAGES/link-advert.po b/core/priv/gettext/es/LC_MESSAGES/link-advert.po index 6d2677b0cc..f2909f31d9 100644 --- a/core/priv/gettext/es/LC_MESSAGES/link-advert.po +++ b/core/priv/gettext/es/LC_MESSAGES/link-advert.po @@ -94,3 +94,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "Aquí encontrarás los estudios más recientes publicados en el pool de panl. Haz clic en un estudio para leer más detalles." + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "Año mínimo de nacimiento" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "Año máximo de nacimiento" diff --git a/core/priv/gettext/it/LC_MESSAGES/link-advert.po b/core/priv/gettext/it/LC_MESSAGES/link-advert.po index 1a384ccdef..d12e937ab2 100644 --- a/core/priv/gettext/it/LC_MESSAGES/link-advert.po +++ b/core/priv/gettext/it/LC_MESSAGES/link-advert.po @@ -94,3 +94,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "Qui di seguito trovi i più recenti studi pubblicati nel pool di panl. Clicca su uno studio per leggere ulteriori dettagli." + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "Anno di nascita minimo" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "Anno di nascita massimo" diff --git a/core/priv/gettext/link-advert.pot b/core/priv/gettext/link-advert.pot index 9128e172e8..92beff3f23 100644 --- a/core/priv/gettext/link-advert.pot +++ b/core/priv/gettext/link-advert.pot @@ -93,3 +93,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "" diff --git a/core/priv/gettext/nl/LC_MESSAGES/link-advert.po b/core/priv/gettext/nl/LC_MESSAGES/link-advert.po index eccffcb7cc..c26d7c875e 100644 --- a/core/priv/gettext/nl/LC_MESSAGES/link-advert.po +++ b/core/priv/gettext/nl/LC_MESSAGES/link-advert.po @@ -93,3 +93,11 @@ msgstr "" #, elixir-autogen, elixir-format msgid "submission.available.sub_heading" msgstr "Hieronder vind je de meest recente studies die in panl pool gepubliceerd zijn. Klik op een studie om meer details te lezen." + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.min_label" +msgstr "Minimaal geboortejaar" + +#, elixir-autogen, elixir-format +msgid "submission.criteria.birth_years.max_label" +msgstr "Maximaal geboortejaar" diff --git a/core/priv/repo/migrations/20250918140818_add_birth_year_fields_to_eligibility_criteria.exs b/core/priv/repo/migrations/20250918140818_add_birth_year_fields_to_eligibility_criteria.exs new file mode 100644 index 0000000000..176ae76ffb --- /dev/null +++ b/core/priv/repo/migrations/20250918140818_add_birth_year_fields_to_eligibility_criteria.exs @@ -0,0 +1,17 @@ +defmodule Core.Repo.Migrations.AddBirthYearFieldsToEligibilityCriteria do + use Ecto.Migration + + def up do + alter table(:eligibility_criteria) do + add(:min_birth_year, :integer) + add(:max_birth_year, :integer) + end + end + + def down do + alter table(:eligibility_criteria) do + remove(:min_birth_year) + remove(:max_birth_year) + end + end +end diff --git a/core/systems/advert/advert_helpers.ex b/core/systems/advert/advert_helpers.ex new file mode 100644 index 0000000000..0dac3715b6 --- /dev/null +++ b/core/systems/advert/advert_helpers.ex @@ -0,0 +1,61 @@ +defmodule Systems.Advert.AdvertHelpers do + @moduledoc """ + Logic is based on older logic from the submission_view. + Receives a submission and user, performs filtering logic and returns a list of labels that are used to populate the exclude-adverts selector. + """ + + alias Systems.{Project, Assignment} + alias Systems.Advert + + def update_adverts(%{user: user, submission: submission}) do + %{ + id: advert_id, + assignment: + %{ + excluded: excluded_assignments + } = assignment + } = + Advert.Public.get_by_submission(submission, assignment: [:excluded]) + + excluded_assignment_ids = Enum.map(excluded_assignments, & &1.id) + advert_labels = get_advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) + excluded_user_ids = Assignment.Public.list_user_ids(excluded_assignment_ids) + + %{ + assignment: assignment, + advert_labels: advert_labels, + excluded_user_ids: excluded_user_ids + } + end + + defp get_advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) do + # for each user owned project + Project.Public.list_owned_projects(user, preload: Project.Model.preload_graph(:down)) + # get all items in each project + |> Enum.flat_map(& &1.root.items) + # reject items without adverts + |> Enum.reject(&(&1.advert == nil)) + # loop through the project's adverts + |> Enum.map(& &1.advert) + # reject nil adverts + |> Enum.reject(&is_nil(&1)) + # reject adverts that have no related assignment + |> Enum.reject(&(&1.assignment_id == nil)) + # reject the advert_id (the advert being edited) + |> Enum.filter(&(&1.id != advert_id)) + # convert to labels + |> Enum.map(&to_label(&1, excluded_assignment_ids)) + end + + defp to_label( + %Advert.Model{ + id: id, + promotion: %{title: title}, + assignment_id: assignment_id + }, + excluded_assignment_ids + ) do + excluded = Enum.member?(excluded_assignment_ids, assignment_id) + %{id: to_string(id), value: title, active: excluded} + end +end diff --git a/core/systems/advert/selector_labels.ex b/core/systems/advert/selector_labels.ex new file mode 100644 index 0000000000..953e50582e --- /dev/null +++ b/core/systems/advert/selector_labels.ex @@ -0,0 +1,34 @@ +defmodule Systems.Advert.SelectorLabels do + @moduledoc """ + Builds selector option labels for pools using the director's inclusion criteria. + Extracted from SubmissionView to improve separation of concerns and reduce code complexity. + """ + + alias Frameworks.Concept.Directable + alias Systems.Pool + + @enum_map %{ + genders: Core.Enums.Genders, + native_languages: Core.Enums.NativeLanguages + } + + @doc """ + Return a map of selector field => labels for the given pool and criteria. + """ + def selector_option_labels(pool, criteria) do + Directable.director(pool).inclusion_criteria() + |> Enum.map(&get_selector_labels(&1, criteria)) + |> Enum.reject(&is_nil/1) + |> Map.new() + end + + # birth_years does not have labels as it is not a selector + defp get_selector_labels(:birth_years, %Pool.CriteriaModel{}), do: nil + + defp get_selector_labels(field, %Pool.CriteriaModel{} = criteria) when is_atom(field) do + case Map.get(@enum_map, field) do + nil -> nil + enum_module -> {field, enum_module.labels(Map.get(criteria, field))} + end + end +end diff --git a/core/systems/advert/submission_view.ex b/core/systems/advert/submission_view.ex index e9223d2494..da5df324a0 100644 --- a/core/systems/advert/submission_view.ex +++ b/core/systems/advert/submission_view.ex @@ -1,22 +1,15 @@ defmodule Systems.Advert.SubmissionView do use CoreWeb.LiveForm - alias Core.Enums.{Genders, NativeLanguages} alias Frameworks.Pixel.Selector alias Frameworks.Pixel.Text - alias Frameworks.Concept.Directable + + alias Systems.Advert.AdvertHelpers + alias Systems.Advert.SelectorLabels alias Systems.Advert - alias Systems.Project - alias Systems.Assignment alias Systems.Pool - @enums_mapping %{ - genders: Genders, - native_languages: NativeLanguages - } - - # Update adverts only @impl true def update( _, @@ -25,7 +18,7 @@ defmodule Systems.Advert.SubmissionView do { :ok, socket - |> update_adverts() + |> update_adverts_list() |> compose_child(:exclude_adverts) |> update_ui() } @@ -48,13 +41,16 @@ defmodule Systems.Advert.SubmissionView do |> assign(entity: criteria) |> assign(submission: submission) |> assign(user: user) - |> update_adverts() + |> assign(changeset: Pool.CriteriaModel.changeset(criteria, %{})) + |> update_adverts_list() |> update_ui() } end - defp compose_inclusion_selectors(%{assigns: %{inclusion_labels: inclusion_labels}} = socket) do - inclusion_labels + defp compose_inclusion_selectors( + %{assigns: %{selector_option_labels: selector_option_labels}} = socket + ) do + selector_option_labels |> Map.keys() |> Enum.reduce(socket, fn key, socket -> compose_child(socket, key) end) end @@ -72,8 +68,8 @@ defmodule Systems.Advert.SubmissionView do end @impl true - def compose(key, %{inclusion_labels: inclusion_labels}) do - items = Map.get(inclusion_labels, key) + def compose(:genders, %{selector_option_labels: selector_option_labels}) do + items = Map.get(selector_option_labels, :genders) %{ module: Selector, @@ -85,40 +81,14 @@ defmodule Systems.Advert.SubmissionView do } end - defp update_adverts(%{assigns: %{user: user, submission: submission}} = socket) do - %{id: advert_id, assignment: %{excluded: excluded_assignments} = assignment} = - Advert.Public.get_by_submission(submission, assignment: [:excluded]) - - excluded_assignment_ids = - excluded_assignments - |> Enum.map(& &1.id) - - advert_labels = - Project.Public.list_owned_projects(user, preload: Project.Model.preload_graph(:down)) - |> Enum.flat_map(& &1.root.items) - |> Enum.reject(&(&1.advert == nil)) - |> Enum.map(& &1.advert) - |> Enum.filter(&(&1.id != advert_id)) - |> Enum.map(&to_label(&1, excluded_assignment_ids)) - - excluded_user_ids = Assignment.Public.list_user_ids(excluded_assignment_ids) - + defp update_adverts_list(%{assigns: _} = socket) do socket - |> assign(assignment: assignment) - |> assign(advert_labels: advert_labels) - |> assign(excluded_user_ids: excluded_user_ids) - end - - defp to_label( - %Advert.Model{ - id: id, - promotion: %{title: title}, - assignment_id: assignment_id - }, - excluded_assignment_ids - ) do - excluded = excluded_assignment_ids |> Enum.member?(assignment_id) - %{id: id, value: title, active: excluded} + |> assign( + AdvertHelpers.update_adverts(%{ + user: socket.assigns.user, + submission: socket.assigns.submission + }) + ) end defp update_ui(%{assigns: %{entity: criteria}} = socket) do @@ -137,43 +107,28 @@ defmodule Systems.Advert.SubmissionView do } = socket, criteria ) do - inclusion_labels = - Directable.director(pool).inclusion_criteria() - |> Enum.map(&get_inclusion_labels(&1, criteria)) - |> Map.new() + selector_option_labels = SelectorLabels.selector_option_labels(pool, criteria) - included_user_ids = + user_ids_in_pool = pool |> Pool.Public.list_participants() |> Enum.map(& &1.id) - pool_size = Enum.count(included_user_ids) + pool_size = Enum.count(user_ids_in_pool) pool_title = Pool.Model.title(pool_name) sample_size = - Pool.Public.count_eligitable_users(criteria, included_user_ids, excluded_user_ids) + Pool.Public.count_eligitable_users(criteria, user_ids_in_pool, excluded_user_ids) socket |> assign( - inclusion_labels: inclusion_labels, + selector_option_labels: selector_option_labels, sample_size: sample_size, pool_size: pool_size, pool_title: pool_title ) end - defp get_inclusion_labels(field, %Pool.CriteriaModel{} = criteria) when is_atom(field) do - case Map.get(@enums_mapping, field) do - nil -> - nil - - enum_module -> - values = Map.get(criteria, field) - {field, enum_module.labels(values)} - end - end - - # Saving def save(socket, %Pool.CriteriaModel{} = entity, attrs) do changeset = Pool.CriteriaModel.changeset(entity, attrs) @@ -182,10 +137,10 @@ defmodule Systems.Advert.SubmissionView do |> update_ui() end - defp inclusion_title(:genders), do: dgettext("eyra-account", "features.gender.title") + defp inclusion_criterium_title(:genders), do: dgettext("eyra-account", "features.gender.title") - defp inclusion_title(:native_languages), - do: dgettext("eyra-account", "features.nativelanguage.title") + defp inclusion_criterium_title(:birth_years), + do: dgettext("eyra-account", "features.birthyear.title") @impl true def handle_event( @@ -197,19 +152,16 @@ defmodule Systems.Advert.SubmissionView do }, %{assigns: %{assignment: assignment}} = socket ) do - socket = - save_closure(socket, fn socket -> - Advert.Public.handle_exclusion(assignment, current_items) - - excluded_user_ids = Advert.Public.list_excluded_user_ids(excluded_advert_ids) - - socket - |> assign(excluded_user_ids: excluded_user_ids) - |> update_ui() - |> flash_persister_saved() - end) + Advert.Public.handle_exclusion(assignment, current_items) + excluded_user_ids = Advert.Public.list_excluded_user_ids(excluded_advert_ids) - {:noreply, socket} + { + :noreply, + socket + |> assign(excluded_user_ids: excluded_user_ids) + |> update_ui() + |> flash_persister_saved() + } end @impl true @@ -219,17 +171,27 @@ defmodule Systems.Advert.SubmissionView do active_item_ids: selected_values, source: %{name: criteria_field} }, - %{assigns: %{entity: criteria}} = socket + %{ + assigns: %{entity: criteria} + } = socket ) - when criteria_field in [:genders, :native_languages, :dominant_hands] do + when criteria_field == :genders do attrs = %{criteria_field => selected_values} - socket = - save_closure(socket, fn socket -> - save(socket, criteria, attrs) - end) + {:noreply, save(socket, criteria, attrs)} + end + + @impl true + def handle_event("change", %{"criteria_model" => attrs}, %{assigns: %{entity: entity}} = socket) do + changeset = Pool.CriteriaModel.changeset(entity, attrs) - {:noreply, socket} + { + :noreply, + socket + |> save(changeset) + |> assign(entity: Ecto.Changeset.apply_changes(changeset)) + |> update_ui() + } end @impl true @@ -257,13 +219,29 @@ defmodule Systems.Advert.SubmissionView do <.spacing value="M" />
- <%= for field <- Map.keys(@inclusion_labels) do %> + <%!-- This is responsible for rendering all selector childs, with their respective options. --%> + <%= for field <- Map.keys(@selector_option_labels) do %> +
+ <%= inclusion_criterium_title(field) %> + <.spacing value="S" /> + <.child name={field} fabric={@fabric} /> +
+ <% end %> +
- <%= inclusion_title(field) %> + <%!-- This renders the min-max birth years number inputs --%> + + <%= inclusion_criterium_title(:birth_years) %> + <.spacing value="S" /> - <.child name={field} fabric={@fabric} /> + <.form :let={form} for={@changeset} phx-change="change" phx-target={@myself}> +
+ <.number_input form={form} field={:min_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.min_label")} /> + <.number_input form={form} field={:max_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.max_label")} /> +
+
- <% end %> +
<.spacing value="XL" /> @@ -278,7 +256,6 @@ defmodule Systems.Advert.SubmissionView do <%= dgettext("link-advert", "no.previous.adverts.available") %> <% else %> <.child name={:exclude_adverts} fabric={@fabric} /> - <% end %> <.spacing value="XL" /> diff --git a/core/systems/citizen/_director.ex b/core/systems/citizen/_director.ex index 74ab018b98..6cacd52c4a 100644 --- a/core/systems/citizen/_director.ex +++ b/core/systems/citizen/_director.ex @@ -29,7 +29,7 @@ defmodule Systems.Citizen.Director do @impl true def inclusion_criteria() do - [:genders] + [:genders, :birth_years] end @impl true diff --git a/core/systems/pool/_public.ex b/core/systems/pool/_public.ex index dafaf28c00..1d712b16ad 100644 --- a/core/systems/pool/_public.ex +++ b/core/systems/pool/_public.ex @@ -297,7 +297,9 @@ defmodule Systems.Pool.Public do def count_eligitable_users( %Pool.CriteriaModel{ - genders: genders + genders: genders, + min_birth_year: min_birth_year, + max_birth_year: max_birth_year }, include, exclude @@ -306,6 +308,7 @@ defmodule Systems.Pool.Public do query_count_users(include, exclude) |> optional_where(:gender, genders) + |> optional_where_birth_year(min_birth_year, max_birth_year) |> Repo.one() end @@ -331,6 +334,24 @@ defmodule Systems.Pool.Public do where(query, [user, features], field(features, ^field_name) in ^values) end + defp optional_where_birth_year(query, nil, nil), do: query + + defp optional_where_birth_year(query, min_year, nil) do + where(query, [user, features], field(features, :birth_year) >= ^min_year) + end + + defp optional_where_birth_year(query, nil, max_year) do + where(query, [user, features], field(features, :birth_year) <= ^max_year) + end + + defp optional_where_birth_year(query, min_year, max_year) do + where( + query, + [user, features], + field(features, :birth_year) >= ^min_year and field(features, :birth_year) <= ^max_year + ) + end + def target_achieved?( %Pool.Model{target: target}, %{balance_credit: balance_credit} diff --git a/core/systems/pool/criteria_model.ex b/core/systems/pool/criteria_model.ex index 1e35605319..c0f84e97e6 100644 --- a/core/systems/pool/criteria_model.ex +++ b/core/systems/pool/criteria_model.ex @@ -12,28 +12,61 @@ defmodule Systems.Pool.CriteriaModel do schema "eligibility_criteria" do field(:genders, {:array, Ecto.Enum}, values: Genders.schema_values()) + field(:min_birth_year, :integer) + field(:max_birth_year, :integer) belongs_to(:submission, SubmissionModel) timestamps() end - @fields ~w(genders)a + @fields ~w(genders min_birth_year max_birth_year)a @doc false def changeset(profile, attrs) do profile |> cast(attrs, @fields) + |> validate_min_max() + end + + defp validate_min_max(changeset) do + min_year = get_field(changeset, :min_birth_year) + max_year = get_field(changeset, :max_birth_year) + + cond do + min_year && max_year && min_year > max_year -> + add_error(changeset, :max_birth_year, "must be greater than or equal to min birth year") + + true -> + changeset + end end def eligitable?(nil, nil), do: true def eligitable?(criteria, nil) do - meets?(criteria.genders, nil) + meets?(criteria.genders, nil) and meets_birth_year?(criteria, nil) + end + + def eligitable?(criteria, %{gender: gender, birth_year: birth_year}) do + meets?(criteria.genders, gender) and meets_birth_year?(criteria, birth_year) end def eligitable?(criteria, %{gender: gender}) do - meets?(criteria.genders, gender) + meets?(criteria.genders, gender) and meets_birth_year?(criteria, nil) + end + + defp meets_birth_year?(criteria, birth_year) do + min_year = criteria.min_birth_year + max_year = criteria.max_birth_year + + cond do + min_year == nil and max_year == nil -> true + birth_year == nil -> false + min_year != nil and birth_year < min_year -> false + max_year != nil and birth_year > max_year -> false + true -> true + end end defp meets?(field, value) when is_list(value) do diff --git a/core/systems/student/_director.ex b/core/systems/student/_director.ex index 593dd26aab..89d996d8b7 100644 --- a/core/systems/student/_director.ex +++ b/core/systems/student/_director.ex @@ -27,7 +27,7 @@ defmodule Systems.Student.Director do @impl true def inclusion_criteria() do - [:genders, :native_languages] + [:genders, :birth_years] end @impl true From 6d853c96f57c1c02a7f42f10f707d4609a898376 Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Thu, 2 Oct 2025 16:58:41 +0200 Subject: [PATCH 2/7] fix: remove birth_years from inclusion criteria in Director modules --- core/systems/citizen/_director.ex | 2 +- core/systems/student/_director.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/systems/citizen/_director.ex b/core/systems/citizen/_director.ex index 6cacd52c4a..74ab018b98 100644 --- a/core/systems/citizen/_director.ex +++ b/core/systems/citizen/_director.ex @@ -29,7 +29,7 @@ defmodule Systems.Citizen.Director do @impl true def inclusion_criteria() do - [:genders, :birth_years] + [:genders] end @impl true diff --git a/core/systems/student/_director.ex b/core/systems/student/_director.ex index 89d996d8b7..090ff8bf63 100644 --- a/core/systems/student/_director.ex +++ b/core/systems/student/_director.ex @@ -27,7 +27,7 @@ defmodule Systems.Student.Director do @impl true def inclusion_criteria() do - [:genders, :birth_years] + [:genders] end @impl true From 5dba49d8c5d48b9c3546833778944c092d15b4f0 Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Fri, 3 Oct 2025 09:58:46 +0200 Subject: [PATCH 3/7] feat: add filtering for 'prefer not to say' option in gender selection --- core/systems/advert/selector_labels.ex | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/systems/advert/selector_labels.ex b/core/systems/advert/selector_labels.ex index 953e50582e..a313617bad 100644 --- a/core/systems/advert/selector_labels.ex +++ b/core/systems/advert/selector_labels.ex @@ -25,6 +25,17 @@ defmodule Systems.Advert.SelectorLabels do # birth_years does not have labels as it is not a selector defp get_selector_labels(:birth_years, %Pool.CriteriaModel{}), do: nil + # filters the "prefer not to say" option as this is not a valid inclusion criterium + defp get_selector_labels(:genders, %Pool.CriteriaModel{} = criteria) do + enum_module = Map.get(@enum_map, :genders) + + allowed_values = + enum_module.values() + |> Enum.reject(&(&1 == :prefer_not_to_say)) + + {:genders, enum_module.labels(Map.get(criteria, :genders, []), allowed_values)} + end + defp get_selector_labels(field, %Pool.CriteriaModel{} = criteria) when is_atom(field) do case Map.get(@enum_map, field) do nil -> nil From 04bfcbaf2bc7a19c6b6bb568c3961d69b202c5db Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Fri, 3 Oct 2025 15:14:40 +0200 Subject: [PATCH 4/7] feat: implement SubmissionViewBuilder --- core/systems/advert/_presenter.ex | 1 + core/systems/advert/_switch.ex | 9 + core/systems/advert/advert_helpers.ex | 61 ------ core/systems/advert/content_page_builder.ex | 5 +- core/systems/advert/selector_labels.ex | 45 ----- core/systems/advert/submission_view.ex | 189 ++++++------------ .../systems/advert/submission_view_builder.ex | 134 +++++++++++++ core/systems/pool/_public.ex | 1 + core/systems/pool/criteria_model.ex | 11 + 9 files changed, 222 insertions(+), 234 deletions(-) delete mode 100644 core/systems/advert/advert_helpers.ex delete mode 100644 core/systems/advert/selector_labels.ex create mode 100644 core/systems/advert/submission_view_builder.ex diff --git a/core/systems/advert/_presenter.ex b/core/systems/advert/_presenter.ex index 2f6f2ee0e1..6a2a367141 100644 --- a/core/systems/advert/_presenter.ex +++ b/core/systems/advert/_presenter.ex @@ -26,4 +26,5 @@ defmodule Systems.Advert.Presenter do defp builder(Advert.ContentPage), do: Advert.ContentPageBuilder defp builder(Promotion.LandingPage), do: Advert.PromotionLandingPageBuilder + defp builder(Advert.SubmissionView), do: Advert.SubmissionViewBuilder end diff --git a/core/systems/advert/_switch.ex b/core/systems/advert/_switch.ex index 9b29c0774b..3b7da8fdbb 100644 --- a/core/systems/advert/_switch.ex +++ b/core/systems/advert/_switch.ex @@ -27,6 +27,15 @@ defmodule Systems.Advert.Switch do :ok end + @impl true + def intercept({:submission, _} = signal, %{submission: submission} = message) do + if advert = Advert.Public.get_by_submission(submission, Advert.Model.preload_graph(:down)) do + dispatch!({:advert, signal}, Map.merge(message, %{advert: advert})) + end + + :ok + end + @impl true def intercept({:advert, _} = signal, message) do handle(signal, message) diff --git a/core/systems/advert/advert_helpers.ex b/core/systems/advert/advert_helpers.ex deleted file mode 100644 index 0dac3715b6..0000000000 --- a/core/systems/advert/advert_helpers.ex +++ /dev/null @@ -1,61 +0,0 @@ -defmodule Systems.Advert.AdvertHelpers do - @moduledoc """ - Logic is based on older logic from the submission_view. - Receives a submission and user, performs filtering logic and returns a list of labels that are used to populate the exclude-adverts selector. - """ - - alias Systems.{Project, Assignment} - alias Systems.Advert - - def update_adverts(%{user: user, submission: submission}) do - %{ - id: advert_id, - assignment: - %{ - excluded: excluded_assignments - } = assignment - } = - Advert.Public.get_by_submission(submission, assignment: [:excluded]) - - excluded_assignment_ids = Enum.map(excluded_assignments, & &1.id) - advert_labels = get_advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) - excluded_user_ids = Assignment.Public.list_user_ids(excluded_assignment_ids) - - %{ - assignment: assignment, - advert_labels: advert_labels, - excluded_user_ids: excluded_user_ids - } - end - - defp get_advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) do - # for each user owned project - Project.Public.list_owned_projects(user, preload: Project.Model.preload_graph(:down)) - # get all items in each project - |> Enum.flat_map(& &1.root.items) - # reject items without adverts - |> Enum.reject(&(&1.advert == nil)) - # loop through the project's adverts - |> Enum.map(& &1.advert) - # reject nil adverts - |> Enum.reject(&is_nil(&1)) - # reject adverts that have no related assignment - |> Enum.reject(&(&1.assignment_id == nil)) - # reject the advert_id (the advert being edited) - |> Enum.filter(&(&1.id != advert_id)) - # convert to labels - |> Enum.map(&to_label(&1, excluded_assignment_ids)) - end - - defp to_label( - %Advert.Model{ - id: id, - promotion: %{title: title}, - assignment_id: assignment_id - }, - excluded_assignment_ids - ) do - excluded = Enum.member?(excluded_assignment_ids, assignment_id) - %{id: to_string(id), value: title, active: excluded} - end -end diff --git a/core/systems/advert/content_page_builder.ex b/core/systems/advert/content_page_builder.ex index d0dfad1a89..345018a916 100644 --- a/core/systems/advert/content_page_builder.ex +++ b/core/systems/advert/content_page_builder.ex @@ -79,14 +79,13 @@ defmodule Systems.Advert.ContentPageBuilder do defp create_tab( :pool, - %{submission: submission}, + %Advert.Model{submission: _} = advert, show_errors, %{fabric: fabric, current_user: user} ) do child = Fabric.prepare_child(fabric, :submission_form, Advert.SubmissionView, %{ - entity: submission, - user: user + vm: Advert.SubmissionViewBuilder.view_model(advert, %{current_user: user}) }) %{ diff --git a/core/systems/advert/selector_labels.ex b/core/systems/advert/selector_labels.ex deleted file mode 100644 index a313617bad..0000000000 --- a/core/systems/advert/selector_labels.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Systems.Advert.SelectorLabels do - @moduledoc """ - Builds selector option labels for pools using the director's inclusion criteria. - Extracted from SubmissionView to improve separation of concerns and reduce code complexity. - """ - - alias Frameworks.Concept.Directable - alias Systems.Pool - - @enum_map %{ - genders: Core.Enums.Genders, - native_languages: Core.Enums.NativeLanguages - } - - @doc """ - Return a map of selector field => labels for the given pool and criteria. - """ - def selector_option_labels(pool, criteria) do - Directable.director(pool).inclusion_criteria() - |> Enum.map(&get_selector_labels(&1, criteria)) - |> Enum.reject(&is_nil/1) - |> Map.new() - end - - # birth_years does not have labels as it is not a selector - defp get_selector_labels(:birth_years, %Pool.CriteriaModel{}), do: nil - - # filters the "prefer not to say" option as this is not a valid inclusion criterium - defp get_selector_labels(:genders, %Pool.CriteriaModel{} = criteria) do - enum_module = Map.get(@enum_map, :genders) - - allowed_values = - enum_module.values() - |> Enum.reject(&(&1 == :prefer_not_to_say)) - - {:genders, enum_module.labels(Map.get(criteria, :genders, []), allowed_values)} - end - - defp get_selector_labels(field, %Pool.CriteriaModel{} = criteria) when is_atom(field) do - case Map.get(@enum_map, field) do - nil -> nil - enum_module -> {field, enum_module.labels(Map.get(criteria, field))} - end - end -end diff --git a/core/systems/advert/submission_view.ex b/core/systems/advert/submission_view.ex index da5df324a0..314d733b4c 100644 --- a/core/systems/advert/submission_view.ex +++ b/core/systems/advert/submission_view.ex @@ -3,52 +3,21 @@ defmodule Systems.Advert.SubmissionView do alias Frameworks.Pixel.Selector alias Frameworks.Pixel.Text - - alias Systems.Advert.AdvertHelpers - alias Systems.Advert.SelectorLabels - alias Systems.Advert alias Systems.Pool @impl true - def update( - _, - %{assigns: %{entity: _}} = socket - ) do + def update(%{vm: vm}, socket) do { :ok, socket - |> update_adverts_list() - |> compose_child(:exclude_adverts) - |> update_ui() - } - end - - # Initial update - @impl true - def update( - %{ - id: id, - entity: %{criteria: criteria} = submission, - user: user - }, - socket - ) do - { - :ok, - socket - |> assign(id: id) - |> assign(entity: criteria) - |> assign(submission: submission) - |> assign(user: user) - |> assign(changeset: Pool.CriteriaModel.changeset(criteria, %{})) - |> update_adverts_list() - |> update_ui() + |> assign(vm: vm) + |> build_children() } end defp compose_inclusion_selectors( - %{assigns: %{selector_option_labels: selector_option_labels}} = socket + %{assigns: %{vm: %{selector_option_labels: selector_option_labels}}} = socket ) do selector_option_labels |> Map.keys() @@ -56,7 +25,7 @@ defmodule Systems.Advert.SubmissionView do end @impl true - def compose(:exclude_adverts, %{advert_labels: items}) do + def compose(:exclude_adverts, %{vm: %{advert_labels: items}}) do %{ module: Selector, params: %{ @@ -68,7 +37,7 @@ defmodule Systems.Advert.SubmissionView do end @impl true - def compose(:genders, %{selector_option_labels: selector_option_labels}) do + def compose(:genders, %{vm: %{selector_option_labels: selector_option_labels}}) do items = Map.get(selector_option_labels, :genders) %{ @@ -81,60 +50,15 @@ defmodule Systems.Advert.SubmissionView do } end - defp update_adverts_list(%{assigns: _} = socket) do - socket - |> assign( - AdvertHelpers.update_adverts(%{ - user: socket.assigns.user, - submission: socket.assigns.submission - }) - ) - end - - defp update_ui(%{assigns: %{entity: criteria}} = socket) do - socket - |> compose_child(:exclude_adverts) - |> update_ui(criteria) - |> compose_inclusion_selectors() - end - - defp update_ui( - %{ - assigns: %{ - submission: %{pool: %{name: pool_name} = pool}, - excluded_user_ids: excluded_user_ids - } - } = socket, - criteria + defp persist_criteria_changes( + %{assigns: %{vm: %{entity: %{criteria: criteria}}}} = socket, + attrs ) do - selector_option_labels = SelectorLabels.selector_option_labels(pool, criteria) - - user_ids_in_pool = - pool - |> Pool.Public.list_participants() - |> Enum.map(& &1.id) - - pool_size = Enum.count(user_ids_in_pool) - pool_title = Pool.Model.title(pool_name) - - sample_size = - Pool.Public.count_eligitable_users(criteria, user_ids_in_pool, excluded_user_ids) - - socket - |> assign( - selector_option_labels: selector_option_labels, - sample_size: sample_size, - pool_size: pool_size, - pool_title: pool_title - ) - end - - def save(socket, %Pool.CriteriaModel{} = entity, attrs) do - changeset = Pool.CriteriaModel.changeset(entity, attrs) + changeset = Pool.CriteriaModel.changeset(criteria, attrs) socket |> save(changeset) - |> update_ui() + |> flash_persister_saved() end defp inclusion_criterium_title(:genders), do: dgettext("eyra-account", "features.gender.title") @@ -150,7 +74,7 @@ defmodule Systems.Advert.SubmissionView do source: %{name: :exclude_adverts}, current_items: current_items }, - %{assigns: %{assignment: assignment}} = socket + %{assigns: %{vm: %{assignment: assignment}}} = socket ) do Advert.Public.handle_exclusion(assignment, current_items) excluded_user_ids = Advert.Public.list_excluded_user_ids(excluded_advert_ids) @@ -159,7 +83,6 @@ defmodule Systems.Advert.SubmissionView do :noreply, socket |> assign(excluded_user_ids: excluded_user_ids) - |> update_ui() |> flash_persister_saved() } end @@ -171,26 +94,24 @@ defmodule Systems.Advert.SubmissionView do active_item_ids: selected_values, source: %{name: criteria_field} }, - %{ - assigns: %{entity: criteria} - } = socket + socket ) when criteria_field == :genders do attrs = %{criteria_field => selected_values} - {:noreply, save(socket, criteria, attrs)} + { + :noreply, + socket + |> persist_criteria_changes(attrs) + } end @impl true - def handle_event("change", %{"criteria_model" => attrs}, %{assigns: %{entity: entity}} = socket) do - changeset = Pool.CriteriaModel.changeset(entity, attrs) - + def handle_event("change", %{"criteria_model" => attrs}, socket) do { :noreply, socket - |> save(changeset) - |> assign(entity: Ecto.Changeset.apply_changes(changeset)) - |> update_ui() + |> persist_criteria_changes(attrs) } end @@ -205,9 +126,9 @@ defmodule Systems.Advert.SubmissionView do <%= raw( dgettext("link-advert", "submission.criteria.status", - sample: "#{@sample_size}", - total: @pool_size, - pool: @pool_title + sample: "#{@vm.sample_size}", + total: @vm.pool_size, + pool: @vm.pool_title ) ) %> @@ -219,30 +140,11 @@ defmodule Systems.Advert.SubmissionView do <.spacing value="M" />
- <%!-- This is responsible for rendering all selector childs, with their respective options. --%> - <%= for field <- Map.keys(@selector_option_labels) do %> -
- <%= inclusion_criterium_title(field) %> - <.spacing value="S" /> - <.child name={field} fabric={@fabric} /> -
- <% end %> - -
- <%!-- This renders the min-max birth years number inputs --%> - - <%= inclusion_criterium_title(:birth_years) %> - - <.spacing value="S" /> - <.form :let={form} for={@changeset} phx-change="change" phx-target={@myself}> -
- <.number_input form={form} field={:min_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.min_label")} /> - <.number_input form={form} field={:max_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.max_label")} /> -
- -
+ <.render_inclusion_selectors selector_option_labels={@vm.selector_option_labels} fabric={@fabric} /> + <.render_age_inputs changeset={@vm.changeset} myself={@myself} />
+ <.spacing value="XL" />
@@ -252,7 +154,7 @@ defmodule Systems.Advert.SubmissionView do <%= dgettext("link-advert", "exclusion.select.label") %> <.spacing value="S" /> - <%= if Enum.count(@advert_labels) == 0 do %> + <%= if Enum.count(@vm.advert_labels) == 0 do %> <%= dgettext("link-advert", "no.previous.adverts.available") %> <% else %> <.child name={:exclude_adverts} fabric={@fabric} /> @@ -264,4 +166,41 @@ defmodule Systems.Advert.SubmissionView do
""" end + + defp render_age_inputs(assigns) do + ~H""" +
+ + <%= inclusion_criterium_title(:birth_years) %> + + <.spacing value="S" /> + <.form :let={form} for={@changeset} phx-change="change" phx-target={@myself}> +
+ <.number_input form={form} field={:min_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.min_label")} /> + <.number_input form={form} field={:max_birth_year} label_text={dgettext("link-advert", "submission.criteria.birth_years.max_label")} /> +
+ +
+ """ + end + + defp render_inclusion_selectors(assigns) do + ~H""" +
+ <%= for field <- Map.keys(@selector_option_labels) do %> +
+ <%= inclusion_criterium_title(field) %> + <.spacing value="S" /> + <.child name={field} fabric={@fabric} /> +
+ <% end %> +
+ """ + end + + defp build_children(socket) do + socket + |> compose_child(:exclude_adverts) + |> compose_inclusion_selectors() + end end diff --git a/core/systems/advert/submission_view_builder.ex b/core/systems/advert/submission_view_builder.ex new file mode 100644 index 0000000000..048086f205 --- /dev/null +++ b/core/systems/advert/submission_view_builder.ex @@ -0,0 +1,134 @@ +defmodule Systems.Advert.SubmissionViewBuilder do + alias Frameworks.Concept.Directable + alias Systems.{Advert, Project, Assignment, Pool} + + @enum_map %{ + genders: Core.Enums.Genders, + native_languages: Core.Enums.NativeLanguages + } + + def view_model(%Advert.Model{submission: submission} = advert, %{current_user: user}) do + criteria = submission.criteria + pool = submission.pool + selector_option_labels = get_inclusion_criteria_labels(pool, criteria) + changeset = Pool.CriteriaModel.changeset(criteria, %{}) + + %{ + assignment: assignment, + advert_labels: advert_labels, + excluded_user_ids: excluded_user_ids + } = adverts_state(user, submission) + + %{ + sample_size: sample_size, + pool_size: pool_size, + pool_title: pool_title + } = pool_stats(pool, criteria, excluded_user_ids) + + %{ + advert: advert, + entity: submission, + user: user, + assignment: assignment, + advert_labels: advert_labels, + excluded_user_ids: excluded_user_ids, + selector_option_labels: selector_option_labels, + sample_size: sample_size, + pool_size: pool_size, + pool_title: pool_title, + changeset: changeset + } + end + + defp adverts_state(user, submission) do + %{ + id: advert_id, + assignment: + %{ + excluded: excluded_assignments + } = assignment + } = Advert.Public.get_by_submission(submission, assignment: [:excluded]) + + excluded_assignment_ids = Enum.map(excluded_assignments, & &1.id) + advert_labels = advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) + excluded_user_ids = Assignment.Public.list_user_ids(excluded_assignment_ids) + + %{ + assignment: assignment, + advert_labels: advert_labels, + excluded_user_ids: excluded_user_ids + } + end + + # Build labels for adverts from the user's owned projects, excluding the + # advert currently being edited and adverts without an assignment. + defp advert_labels_for_users_projects(user, advert_id, excluded_assignment_ids) do + Project.Public.list_owned_projects(user, preload: Project.Model.preload_graph(:down)) + |> Enum.flat_map(& &1.root.items) + |> Enum.reject(&(&1.advert == nil)) + |> Enum.map(& &1.advert) + |> Enum.reject(&is_nil(&1)) + |> Enum.reject(&(&1.assignment_id == nil)) + |> Enum.filter(&(&1.id != advert_id)) + |> Enum.map(&to_advert_label(&1, excluded_assignment_ids)) + end + + defp to_advert_label( + %Advert.Model{ + id: id, + promotion: %{title: title}, + assignment_id: assignment_id + }, + excluded_assignment_ids + ) do + excluded = Enum.member?(excluded_assignment_ids, assignment_id) + %{id: to_string(id), value: title, active: excluded} + end + + def get_inclusion_criteria_labels(pool, %Pool.CriteriaModel{} = criteria) do + Directable.director(pool).inclusion_criteria() + |> Enum.map(&labels_for_field(&1, criteria)) + |> Enum.reject(&is_nil/1) + |> Map.new() + end + + # birth_years are handled with number inputs, not selectors + defp labels_for_field(:birth_years, %Pool.CriteriaModel{}), do: nil + + # genders: filter out "prefer_not_to_say" as it is not a valid inclusion criterium + defp labels_for_field(:genders, %Pool.CriteriaModel{} = criteria) do + enum_module = Map.fetch!(@enum_map, :genders) + + allowed_values = + enum_module.values() + |> Enum.reject(&(&1 == :prefer_not_to_say)) + + {:genders, enum_module.labels(Map.get(criteria, :genders, []), allowed_values)} + end + + defp labels_for_field(field, %Pool.CriteriaModel{} = criteria) when is_atom(field) do + case Map.get(@enum_map, field) do + nil -> nil + enum_module -> {field, enum_module.labels(Map.get(criteria, field))} + end + end + + defp pool_stats( + %Pool.Model{name: pool_name} = pool, + %Pool.CriteriaModel{} = criteria, + excluded_user_ids + ) do + user_ids_in_pool = + pool + |> Pool.Public.list_participants() + |> Enum.map(& &1.id) + + pool_size = Enum.count(user_ids_in_pool) + pool_title = Pool.Model.title(pool_name) + + sample_size = + Pool.Public.count_eligitable_users(criteria, user_ids_in_pool, excluded_user_ids) + + %{sample_size: sample_size, pool_size: pool_size, pool_title: pool_title} + end +end diff --git a/core/systems/pool/_public.ex b/core/systems/pool/_public.ex index 1d712b16ad..789b448a8d 100644 --- a/core/systems/pool/_public.ex +++ b/core/systems/pool/_public.ex @@ -276,6 +276,7 @@ defmodule Systems.Pool.Public do |> Multi.update(:criteria, changeset) |> Multi.run(:dispatch, fn _, %{criteria: criteria} -> Signal.Public.dispatch!({:criteria, :updated}, %{criteria: criteria}) + {:ok, true} end) |> Repo.transaction() end diff --git a/core/systems/pool/criteria_model.ex b/core/systems/pool/criteria_model.ex index c0f84e97e6..0b483d7057 100644 --- a/core/systems/pool/criteria_model.ex +++ b/core/systems/pool/criteria_model.ex @@ -77,3 +77,14 @@ defmodule Systems.Pool.CriteriaModel do field == nil || Enum.empty?(field) || Enum.member?(field, value) end end + +defimpl Core.Persister, for: Systems.Pool.CriteriaModel do + alias Systems.Pool + + def save(criteria, changeset) do + case Pool.Public.update(criteria, changeset) do + {:ok, %{criteria: criteria}} -> {:ok, criteria} + _ -> {:error, changeset} + end + end +end From df9c5446f1d35b1d2199cdce5bc4c403e45a7726 Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Fri, 3 Oct 2025 16:26:18 +0200 Subject: [PATCH 5/7] fix: ci changes and restore error messages --- core/systems/advert/submission_view.ex | 11 +++++++++-- core/systems/advert/submission_view_builder.ex | 4 +--- core/systems/pool/criteria_model.ex | 10 ++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/systems/advert/submission_view.ex b/core/systems/advert/submission_view.ex index 314d733b4c..fb4654ff46 100644 --- a/core/systems/advert/submission_view.ex +++ b/core/systems/advert/submission_view.ex @@ -51,16 +51,23 @@ defmodule Systems.Advert.SubmissionView do end defp persist_criteria_changes( - %{assigns: %{vm: %{entity: %{criteria: criteria}}}} = socket, + %{assigns: %{vm: %{entity: %{criteria: criteria} = vm}}} = socket, attrs ) do changeset = Pool.CriteriaModel.changeset(criteria, attrs) socket |> save(changeset) - |> flash_persister_saved() + |> update_vm_changeset() end + defp update_vm_changeset(%{assigns: %{vm: vm, changeset: changeset}} = socket) do + socket + |> assign(vm: Map.put(vm, :changeset, changeset)) + end + + defp update_vm_changeset(socket), do: socket + defp inclusion_criterium_title(:genders), do: dgettext("eyra-account", "features.gender.title") defp inclusion_criterium_title(:birth_years), diff --git a/core/systems/advert/submission_view_builder.ex b/core/systems/advert/submission_view_builder.ex index 048086f205..c82aea692e 100644 --- a/core/systems/advert/submission_view_builder.ex +++ b/core/systems/advert/submission_view_builder.ex @@ -67,9 +67,7 @@ defmodule Systems.Advert.SubmissionViewBuilder do |> Enum.flat_map(& &1.root.items) |> Enum.reject(&(&1.advert == nil)) |> Enum.map(& &1.advert) - |> Enum.reject(&is_nil(&1)) - |> Enum.reject(&(&1.assignment_id == nil)) - |> Enum.filter(&(&1.id != advert_id)) + |> Enum.reject(&(is_nil(&1) or &1.assignment_id == nil or &1.id == advert_id)) |> Enum.map(&to_advert_label(&1, excluded_assignment_ids)) end diff --git a/core/systems/pool/criteria_model.ex b/core/systems/pool/criteria_model.ex index 0b483d7057..b345cdd123 100644 --- a/core/systems/pool/criteria_model.ex +++ b/core/systems/pool/criteria_model.ex @@ -33,12 +33,10 @@ defmodule Systems.Pool.CriteriaModel do min_year = get_field(changeset, :min_birth_year) max_year = get_field(changeset, :max_birth_year) - cond do - min_year && max_year && min_year > max_year -> - add_error(changeset, :max_birth_year, "must be greater than or equal to min birth year") - - true -> - changeset + if min_year && max_year && min_year > max_year do + add_error(changeset, :max_birth_year, "must be greater than or equal to min birth year") + else + changeset end end From 5d1ac9cb311b6a61a8dbab4cb6f20650b65d3d2d Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Fri, 3 Oct 2025 16:40:52 +0200 Subject: [PATCH 6/7] ci: formatting fix --- core/systems/advert/submission_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/systems/advert/submission_view.ex b/core/systems/advert/submission_view.ex index fb4654ff46..3d3ef738ee 100644 --- a/core/systems/advert/submission_view.ex +++ b/core/systems/advert/submission_view.ex @@ -51,7 +51,7 @@ defmodule Systems.Advert.SubmissionView do end defp persist_criteria_changes( - %{assigns: %{vm: %{entity: %{criteria: criteria} = vm}}} = socket, + %{assigns: %{vm: %{entity: %{criteria: criteria}}}} = socket, attrs ) do changeset = Pool.CriteriaModel.changeset(criteria, attrs) From aca330fdd294671e0dd00395019caed67a152528 Mon Sep 17 00:00:00 2001 From: MelchiorKokernoot Date: Fri, 3 Oct 2025 16:47:32 +0200 Subject: [PATCH 7/7] chore: add inotify-tools to Dockerfile to enable hot-reload --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 74422bd282..13d2543f96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN apt-get update && apt-get install -y \ autoconf \ libssl-dev \ libncurses-dev \ + inotify-tools \ && rm -rf /var/lib/apt/lists/* RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen