From 6cfb2f809beaa77651449da27a4199b886e2b0e5 Mon Sep 17 00:00:00 2001 From: phoebus-84 Date: Wed, 15 Apr 2026 19:20:39 +0200 Subject: [PATCH] feat: add orderBy sorting and distinctPrimaryAccountableCount to economicResources - Add EconomicResourceSortField (CREATED_AT, NAME) and SortDirection enums - Add orderBy argument to economicResources query - Thread order_by through Page struct and Connection.parse - Apply sorting in Query.all via apply_order_by/2 - Add EconomicResourcePageInfo type with distinctPrimaryAccountableCount - Add filtered_query/1 and count_distinct_primary_accountable/1 in Query - Merge distinct count into page_info in resolver --- src/zenflows/db/page.ex | 3 +- src/zenflows/gql/connection.ex | 5 ++- src/zenflows/vf/economic_resource/domain.ex | 6 +++ src/zenflows/vf/economic_resource/query.ex | 47 ++++++++++++++++++++- src/zenflows/vf/economic_resource/resolv.ex | 7 ++- src/zenflows/vf/economic_resource/type.ex | 28 +++++++++++- 6 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/zenflows/db/page.ex b/src/zenflows/db/page.ex index d572fdd..842a11b 100644 --- a/src/zenflows/db/page.ex +++ b/src/zenflows/db/page.ex @@ -24,7 +24,7 @@ import Ecto.Query alias Zenflows.DB.{ID, Repo} @enforce_keys ~w[dir cur num filter]a -defstruct [:cur, :num, :filter, dir: :forw] +defstruct [:cur, :num, :filter, :order_by, dir: :forw] @typedoc """ Represents a generic struct that has all required information to @@ -42,6 +42,7 @@ The fields are: cur: nil | ID.t(), num: non_neg_integer(), filter: nil | map(), + order_by: nil | map(), } @doc """ diff --git a/src/zenflows/gql/connection.ex b/src/zenflows/gql/connection.ex index dfb2409..79cb082 100644 --- a/src/zenflows/gql/connection.ex +++ b/src/zenflows/gql/connection.ex @@ -107,6 +107,7 @@ def parse(params) do cur: after_ || before, num: if((n = first || last), do: normalize(n), else: def_page_size()), filter: data[:filter], + order_by: data[:order_by], }} end end @@ -114,8 +115,8 @@ end @doc false @spec changeset(Schema.params()) :: Changeset.t() def changeset(params) do - {%{}, %{after: ID, first: :integer, before: ID, last: :integer, filter: :map}} - |> Changeset.cast(params, [:after, :first, :before, :last, :filter]) + {%{}, %{after: ID, first: :integer, before: ID, last: :integer, filter: :map, order_by: :map}} + |> Changeset.cast(params, [:after, :first, :before, :last, :filter, :order_by]) |> Changeset.validate_number(:first, greater_than_or_equal_to: 0) |> Changeset.validate_number(:last, greater_than_or_equal_to: 0) |> Validate.exist_nand([:first, :last]) diff --git a/src/zenflows/vf/economic_resource/domain.ex b/src/zenflows/vf/economic_resource/domain.ex index 20a7ace..083d55c 100644 --- a/src/zenflows/vf/economic_resource/domain.ex +++ b/src/zenflows/vf/economic_resource/domain.ex @@ -64,6 +64,12 @@ def all!(page \\ Page.new()) do value end +@spec count_distinct_primary_accountable(nil | map()) + :: {:ok, non_neg_integer()} | {:error, Ecto.Changeset.t()} +def count_distinct_primary_accountable(filter_params) do + Query.count_distinct_primary_accountable(filter_params) +end + @spec previous(EconomicResource.t() | Schema.id()) :: [EconomicEvent.t()] def previous(_, _ \\ Page.new()) def previous(%EconomicResource{id: id}, page), do: previous(id, page) diff --git a/src/zenflows/vf/economic_resource/query.ex b/src/zenflows/vf/economic_resource/query.ex index af5bf3e..4ceab06 100644 --- a/src/zenflows/vf/economic_resource/query.ex +++ b/src/zenflows/vf/economic_resource/query.ex @@ -22,12 +22,16 @@ defmodule Zenflows.VF.EconomicResource.Query do import Ecto.Query alias Ecto.{Changeset, Queryable} -alias Zenflows.DB.{ID, Page, Schema, Validate} +alias Zenflows.DB.{ID, Page, Repo, Schema, Validate} alias Zenflows.VF.{EconomicEvent, EconomicResource, SpatialThing} @spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil, order_by: nil}), do: {:ok, EconomicResource} +def all(%{filter: nil, order_by: order_by}) do + {:ok, apply_order_by(EconomicResource, order_by)} +end def all(%{filter: nil}), do: {:ok, EconomicResource} -def all(%{filter: params}) do +def all(%{filter: params} = page) do with {:ok, filters} <- all_validate(params) do {geo_filters, other_filters} = Enum.split_with(filters, fn {k, _} -> k in [:near_lat, :near_long, :near_distance_km] @@ -35,6 +39,7 @@ def all(%{filter: params}) do end) q = Enum.reduce(other_filters, EconomicResource, &all_f(&2, &1)) q = apply_geo_filter(q, Map.new(geo_filters)) + q = apply_order_by(q, Map.get(page, :order_by)) {:ok, q} end end @@ -98,6 +103,44 @@ defp apply_geo_filter(q, %{near_lat: lat, near_long: long, near_distance_km: dis end defp apply_geo_filter(q, _), do: q +@spec apply_order_by(Queryable.t(), map() | nil) :: Queryable.t() +defp apply_order_by(q, nil), do: q +defp apply_order_by(q, %{field: :created_at, direction: :asc}), + do: order_by(q, [x], asc: x.inserted_at) +defp apply_order_by(q, %{field: :created_at, direction: :desc}), + do: order_by(q, [x], desc: x.inserted_at) +defp apply_order_by(q, %{field: :name, direction: :asc}), + do: order_by(q, [x], asc: x.name) +defp apply_order_by(q, %{field: :name, direction: :desc}), + do: order_by(q, [x], desc: x.name) + +@doc "Builds a filtered query from filter params (without pagination)." +@spec filtered_query(nil | map()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def filtered_query(nil), do: {:ok, EconomicResource} +def filtered_query(params) do + with {:ok, filters} <- all_validate(params) do + {geo_filters, other_filters} = Enum.split_with(filters, fn + {k, _} -> k in [:near_lat, :near_long, :near_distance_km] + _ -> false + end) + q = Enum.reduce(other_filters, EconomicResource, &all_f(&2, &1)) + q = apply_geo_filter(q, Map.new(geo_filters)) + {:ok, q} + end +end + +@doc "Counts distinct primary_accountable_id values for the given filter params." +@spec count_distinct_primary_accountable(nil | map()) + :: {:ok, non_neg_integer()} | {:error, Changeset.t()} +def count_distinct_primary_accountable(filter_params) do + with {:ok, q} <- filtered_query(filter_params) do + count = + from(x in q, select: count(x.primary_accountable_id, :distinct)) + |> Repo.one() + {:ok, count} + end +end + @spec all_validate(Schema.params()) :: {:ok, Changeset.data()} | {:error, Changeset.t()} defp all_validate(params) do diff --git a/src/zenflows/vf/economic_resource/resolv.ex b/src/zenflows/vf/economic_resource/resolv.ex index fa2bb79..d190c4f 100644 --- a/src/zenflows/vf/economic_resource/resolv.ex +++ b/src/zenflows/vf/economic_resource/resolv.ex @@ -30,8 +30,11 @@ end def economic_resources(params, _) do with {:ok, page} <- Connection.parse(params), - {:ok, schemas} <- Domain.all(page) do - {:ok, Connection.from_list(schemas, page)} + {:ok, schemas} <- Domain.all(page), + {:ok, count} <- Domain.count_distinct_primary_accountable(page.filter) do + conn = Connection.from_list(schemas, page) + page_info = Map.put(conn.page_info, :distinct_primary_accountable_count, count) + {:ok, %{conn | page_info: page_info}} end end diff --git a/src/zenflows/vf/economic_resource/type.ex b/src/zenflows/vf/economic_resource/type.ex index 12dc607..cadcdbc 100644 --- a/src/zenflows/vf/economic_resource/type.ex +++ b/src/zenflows/vf/economic_resource/type.ex @@ -249,8 +249,33 @@ object :economic_resource_edge do field :node, non_null(:economic_resource) end +enum :economic_resource_sort_field do + value :created_at + value :name +end + +enum :sort_direction do + value :asc + value :desc +end + +input_object :economic_resource_sort_input do + field :field, non_null(:economic_resource_sort_field) + field :direction, non_null(:sort_direction) +end + +object :economic_resource_page_info do + field :start_cursor, :id + field :end_cursor, :id + field :has_previous_page, non_null(:boolean) + field :has_next_page, non_null(:boolean) + field :total_count, :integer + field :page_limit, :integer + field :distinct_primary_accountable_count, :integer +end + object :economic_resource_connection do - field :page_info, non_null(:page_info) + field :page_info, non_null(:economic_resource_page_info) field :edges, non_null(list_of(non_null(:economic_resource_edge))) end @@ -304,6 +329,7 @@ object :query_economic_resource do arg :last, :integer arg :before, :id arg :filter, :economic_resource_filter_params + arg :order_by, :economic_resource_sort_input resolve &Resolv.economic_resources/2 end