diff --git a/elixir/apps/web/lib/web/live/actors/index.ex b/elixir/apps/web/lib/web/live/actors/index.ex index 834eef09b..345ced74b 100644 --- a/elixir/apps/web/lib/web/live/actors/index.ex +++ b/elixir/apps/web/lib/web/live/actors/index.ex @@ -26,20 +26,14 @@ defmodule Web.Actors.Index do def handle_actors_update!(socket, list_opts) do list_opts = Keyword.put(list_opts, :preload, [:last_seen_at, identities: :provider]) - with {:ok, actors, metadata} <- Actors.list_actors(socket.assigns.subject, list_opts) do - {:ok, actor_groups} = Actors.peek_actor_groups(actors, 3, socket.assigns.subject) - - assign(socket, - actors: actors, - actors_metadata: metadata, - actor_groups: actor_groups - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + with {:ok, actors, metadata} <- Actors.list_actors(socket.assigns.subject, list_opts), + {:ok, actor_groups} <- Actors.peek_actor_groups(actors, 3, socket.assigns.subject) do + {:ok, + assign(socket, + actors: actors, + actors_metadata: metadata, + actor_groups: actor_groups + )} end end diff --git a/elixir/apps/web/lib/web/live/actors/show.ex b/elixir/apps/web/lib/web/live/actors/show.ex index 676b02682..7ee0852b7 100644 --- a/elixir/apps/web/lib/web/live/actors/show.ex +++ b/elixir/apps/web/lib/web/live/actors/show.ex @@ -59,16 +59,11 @@ defmodule Web.Actors.Show do with {:ok, identities, metadata} <- Auth.list_identities_for(socket.assigns.actor, socket.assigns.subject, list_opts) do - assign(socket, - identities: identities, - identities_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + identities: identities, + identities_metadata: metadata + )} end end @@ -82,16 +77,11 @@ defmodule Web.Actors.Show do with {:ok, tokens, metadata} <- Tokens.list_tokens_for(socket.assigns.actor, socket.assigns.subject, list_opts) do - assign(socket, - tokens: tokens, - tokens_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + tokens: tokens, + tokens_metadata: metadata + )} end end @@ -100,16 +90,11 @@ defmodule Web.Actors.Show do with {:ok, clients, metadata} <- Clients.list_clients_for(socket.assigns.actor, socket.assigns.subject, list_opts) do - assign(socket, - clients: clients, - clients_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + clients: clients, + clients_metadata: metadata + )} end end @@ -123,16 +108,11 @@ defmodule Web.Actors.Show do with {:ok, flows, metadata} <- Flows.list_flows_for(socket.assigns.actor, socket.assigns.subject, list_opts) do - assign(socket, - flows: flows, - flows_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + flows: flows, + flows_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/clients/index.ex b/elixir/apps/web/lib/web/live/clients/index.ex index 5ff1af8c9..1121860eb 100644 --- a/elixir/apps/web/lib/web/live/clients/index.ex +++ b/elixir/apps/web/lib/web/live/clients/index.ex @@ -30,16 +30,11 @@ defmodule Web.Clients.Index do list_opts = Keyword.put(list_opts, :preload, [:actor, :online?]) with {:ok, clients, metadata} <- Clients.list_clients(socket.assigns.subject, list_opts) do - assign(socket, - clients: clients, - clients_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + clients: clients, + clients_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/clients/show.ex b/elixir/apps/web/lib/web/live/clients/show.ex index 6861c2295..e50c66e7c 100644 --- a/elixir/apps/web/lib/web/live/clients/show.ex +++ b/elixir/apps/web/lib/web/live/clients/show.ex @@ -47,16 +47,11 @@ defmodule Web.Clients.Show do with {:ok, flows, metadata} <- Flows.list_flows_for(socket.assigns.client, socket.assigns.subject, list_opts) do - assign(socket, - flows: flows, - flows_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + flows: flows, + flows_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/groups/edit_actors.ex b/elixir/apps/web/lib/web/live/groups/edit_actors.ex index 15a8e171c..6bfb1670d 100644 --- a/elixir/apps/web/lib/web/live/groups/edit_actors.ex +++ b/elixir/apps/web/lib/web/live/groups/edit_actors.ex @@ -48,16 +48,11 @@ defmodule Web.Groups.EditActors do list_opts = Keyword.put(list_opts, :preload, identities: :provider) with {:ok, actors, metadata} <- Actors.list_actors(socket.assigns.subject, list_opts) do - assign(socket, - actors: actors, - actors_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + actors: actors, + actors_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/groups/index.ex b/elixir/apps/web/lib/web/live/groups/index.ex index 460b7919e..b53929c8f 100644 --- a/elixir/apps/web/lib/web/live/groups/index.ex +++ b/elixir/apps/web/lib/web/live/groups/index.ex @@ -26,20 +26,14 @@ defmodule Web.Groups.Index do def handle_groups_update!(socket, list_opts) do list_opts = Keyword.put(list_opts, :preload, [:provider, created_by_identity: [:actor]]) - with {:ok, groups, metadata} <- Actors.list_groups(socket.assigns.subject, list_opts) do - {:ok, group_actors} = Actors.peek_group_actors(groups, 3, socket.assigns.subject) - - assign(socket, - groups: groups, - groups_metadata: metadata, - group_actors: group_actors - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + with {:ok, groups, metadata} <- Actors.list_groups(socket.assigns.subject, list_opts), + {:ok, group_actors} <- Actors.peek_group_actors(groups, 3, socket.assigns.subject) do + {:ok, + assign(socket, + groups: groups, + groups_metadata: metadata, + group_actors: group_actors + )} end end diff --git a/elixir/apps/web/lib/web/live/groups/show.ex b/elixir/apps/web/lib/web/live/groups/show.ex index f29063ea0..33b7b5c29 100644 --- a/elixir/apps/web/lib/web/live/groups/show.ex +++ b/elixir/apps/web/lib/web/live/groups/show.ex @@ -50,16 +50,11 @@ defmodule Web.Groups.Show do list_opts = Keyword.put(list_opts, :preload, [:last_seen_at, identities: :provider]) with {:ok, actors, metadata} <- Actors.list_actors(socket.assigns.subject, list_opts) do - assign(socket, - actors: actors, - actors_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + actors: actors, + actors_metadata: metadata + )} end end @@ -67,16 +62,11 @@ defmodule Web.Groups.Show do list_opts = Keyword.put(list_opts, :preload, :resource) with {:ok, policies, metadata} <- Policies.list_policies(socket.assigns.subject, list_opts) do - assign(socket, - policies: policies, - policies_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + policies: policies, + policies_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/policies/index.ex b/elixir/apps/web/lib/web/live/policies/index.ex index 2a8ba470a..1d8e2e3b6 100644 --- a/elixir/apps/web/lib/web/live/policies/index.ex +++ b/elixir/apps/web/lib/web/live/policies/index.ex @@ -29,16 +29,11 @@ defmodule Web.Policies.Index do list_opts = Keyword.put(list_opts, :preload, actor_group: [:provider], resource: []) with {:ok, policies, metadata} <- Policies.list_policies(socket.assigns.subject, list_opts) do - assign(socket, - policies: policies, - policies_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + policies: policies, + policies_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/policies/show.ex b/elixir/apps/web/lib/web/live/policies/show.ex index d48537eca..0da97c651 100644 --- a/elixir/apps/web/lib/web/live/policies/show.ex +++ b/elixir/apps/web/lib/web/live/policies/show.ex @@ -45,16 +45,11 @@ defmodule Web.Policies.Show do with {:ok, flows, metadata} <- Flows.list_flows_for(socket.assigns.policy, socket.assigns.subject, list_opts) do - assign(socket, - flows: flows, - flows_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + flows: flows, + flows_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/relay_groups/index.ex b/elixir/apps/web/lib/web/live/relay_groups/index.ex index be8936093..f010da13c 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/index.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/index.ex @@ -33,16 +33,11 @@ defmodule Web.RelayGroups.Index do list_opts = Keyword.put(list_opts, :preload, relays: [:online?]) with {:ok, groups, metadata} <- Relays.list_groups(socket.assigns.subject, list_opts) do - assign(socket, - groups: groups, - groups_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + groups: groups, + groups_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/relay_groups/show.ex b/elixir/apps/web/lib/web/live/relay_groups/show.ex index 5670fb03d..8b99cc499 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/show.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/show.ex @@ -49,16 +49,11 @@ defmodule Web.RelayGroups.Show do list_opts = Keyword.put(list_opts, :preload, [:online?]) with {:ok, relays, metadata} <- Relays.list_relays(socket.assigns.subject, list_opts) do - assign(socket, - relays: relays, - relays_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + relays: relays, + relays_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/resources/index.ex b/elixir/apps/web/lib/web/live/resources/index.ex index 81e5c6962..0e762d1a5 100644 --- a/elixir/apps/web/lib/web/live/resources/index.ex +++ b/elixir/apps/web/lib/web/live/resources/index.ex @@ -31,21 +31,15 @@ defmodule Web.Resources.Index do list_opts = Keyword.put(list_opts, :preload, [:gateway_groups]) with {:ok, resources, metadata} <- - Resources.list_resources(socket.assigns.subject, list_opts) do - {:ok, resource_actor_groups_peek} = - Resources.peek_resource_actor_groups(resources, 3, socket.assigns.subject) - - assign(socket, - resources: resources, - resource_actor_groups_peek: resource_actor_groups_peek, - resources_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + Resources.list_resources(socket.assigns.subject, list_opts), + {:ok, resource_actor_groups_peek} <- + Resources.peek_resource_actor_groups(resources, 3, socket.assigns.subject) do + {:ok, + assign(socket, + resources: resources, + resource_actor_groups_peek: resource_actor_groups_peek, + resources_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/resources/show.ex b/elixir/apps/web/lib/web/live/resources/show.ex index b0def70b5..49b043e17 100644 --- a/elixir/apps/web/lib/web/live/resources/show.ex +++ b/elixir/apps/web/lib/web/live/resources/show.ex @@ -55,16 +55,11 @@ defmodule Web.Resources.Show do list_opts = Keyword.put(list_opts, :preload, actor_group: [:provider], resource: []) with {:ok, policies, metadata} <- Policies.list_policies(socket.assigns.subject, list_opts) do - assign(socket, - policies: policies, - policies_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + policies: policies, + policies_metadata: metadata + )} end end @@ -78,16 +73,11 @@ defmodule Web.Resources.Show do with {:ok, flows, metadata} <- Flows.list_flows_for(socket.assigns.resource, socket.assigns.subject, list_opts) do - assign(socket, - flows: flows, - flows_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + flows: flows, + flows_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/index.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/index.ex index aefbc4101..5d7ce86f7 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/index.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/index.ex @@ -36,16 +36,11 @@ defmodule Web.Settings.IdentityProviders.Index do def handle_providers_update!(socket, list_opts) do with {:ok, providers, metadata} <- Auth.list_providers(socket.assigns.subject, list_opts) do - assign(socket, - providers: providers, - providers_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + providers: providers, + providers_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/sites/gateways/index.ex b/elixir/apps/web/lib/web/live/sites/gateways/index.ex index ef688a046..b149c16de 100644 --- a/elixir/apps/web/lib/web/live/sites/gateways/index.ex +++ b/elixir/apps/web/lib/web/live/sites/gateways/index.ex @@ -41,16 +41,11 @@ defmodule Web.Sites.Gateways.Index do list_opts = Keyword.put(list_opts, :preload, [:online?]) with {:ok, gateways, metadata} <- Gateways.list_gateways(socket.assigns.subject, list_opts) do - assign(socket, - gateways: gateways, - gateways_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + gateways: gateways, + gateways_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/sites/index.ex b/elixir/apps/web/lib/web/live/sites/index.ex index b83afc2b6..7a4054d75 100644 --- a/elixir/apps/web/lib/web/live/sites/index.ex +++ b/elixir/apps/web/lib/web/live/sites/index.ex @@ -31,16 +31,11 @@ defmodule Web.Sites.Index do with {:ok, groups, metadata} <- Gateways.list_groups(socket.assigns.subject, list_opts) do - assign(socket, - groups: groups, - groups_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + groups: groups, + groups_metadata: metadata + )} end end diff --git a/elixir/apps/web/lib/web/live/sites/show.ex b/elixir/apps/web/lib/web/live/sites/show.ex index fd73b7e25..9c663948b 100644 --- a/elixir/apps/web/lib/web/live/sites/show.ex +++ b/elixir/apps/web/lib/web/live/sites/show.ex @@ -63,36 +63,25 @@ defmodule Web.Sites.Show do end) with {:ok, gateways, metadata} <- Gateways.list_gateways(socket.assigns.subject, list_opts) do - assign(socket, - gateways: gateways, - gateways_metadata: metadata - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + {:ok, + assign(socket, + gateways: gateways, + gateways_metadata: metadata + )} end end def handle_resources_update!(socket, list_opts) do with {:ok, resources, metadata} <- - Resources.list_resources(socket.assigns.subject, list_opts) do - {:ok, resource_actor_groups_peek} = - Resources.peek_resource_actor_groups(resources, 3, socket.assigns.subject) - - assign(socket, - resources: resources, - resources_metadata: metadata, - resource_actor_groups_peek: resource_actor_groups_peek - ) - else - {:error, :invalid_cursor} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:unknown_filter, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_type, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, {:invalid_value, _metadata}} -> raise Web.LiveErrors.InvalidRequestError - {:error, _reason} -> raise Web.LiveErrors.NotFoundError + Resources.list_resources(socket.assigns.subject, list_opts), + {:ok, resource_actor_groups_peek} <- + Resources.peek_resource_actor_groups(resources, 3, socket.assigns.subject) do + {:ok, + assign(socket, + resources: resources, + resources_metadata: metadata, + resource_actor_groups_peek: resource_actor_groups_peek + )} end end diff --git a/elixir/apps/web/lib/web/live_table.ex b/elixir/apps/web/lib/web/live_table.ex index ecd2412f7..507192818 100644 --- a/elixir/apps/web/lib/web/live_table.ex +++ b/elixir/apps/web/lib/web/live_table.ex @@ -82,7 +82,13 @@ defmodule Web.LiveTable do defp resource_filter(assigns) do ~H""" - <.form id={"#{@live_table_id}-filters"} for={@form} phx-change="filter" phx-debounce="100"> + <.form + :if={@filters != []} + id={"#{@live_table_id}-filters"} + for={@form} + phx-change="filter" + phx-debounce="100" + > <.input type="hidden" name="table_id" value={@live_table_id} />
push_navigate(socket, to: socket.assigns.uri) + {:ok, socket} -> socket + end end @doc """ @@ -457,31 +467,48 @@ defmodule Web.LiveTable do order_by: List.wrap(order_by) ] - socket - |> maybe_apply_callback(id, list_opts) - |> assign( - filter_form_by_table_id: - put_table_state( - socket, - id, - :filter_form_by_table_id, - filter_to_form(filter, id) - ), - order_by_table_id: - put_table_state( - socket, - id, - :order_by_table_id, - order_by - ), - list_opts_by_table_id: - put_table_state( - socket, - id, - :list_opts_by_table_id, - list_opts + case maybe_apply_callback(socket, id, list_opts) do + {:ok, socket} -> + socket + |> assign( + filter_form_by_table_id: + put_table_state( + socket, + id, + :filter_form_by_table_id, + filter_to_form(filter, id) + ), + order_by_table_id: + put_table_state( + socket, + id, + :order_by_table_id, + order_by + ), + list_opts_by_table_id: + put_table_state( + socket, + id, + :list_opts_by_table_id, + list_opts + ) ) - ) + + {:error, :invalid_cursor} -> + raise Web.LiveErrors.InvalidRequestError + + {:error, {:unknown_filter, _metadata}} -> + raise Web.LiveErrors.InvalidRequestError + + {:error, {:invalid_type, _metadata}} -> + raise Web.LiveErrors.InvalidRequestError + + {:error, {:invalid_value, _metadata}} -> + raise Web.LiveErrors.InvalidRequestError + + {:error, _reason} -> + raise Web.LiveErrors.NotFoundError + end end defp maybe_apply_callback(socket, id, list_opts) do @@ -491,7 +518,7 @@ defmodule Web.LiveTable do callback = Map.fetch!(socket.assigns.callback_by_table_id, id) callback.(socket, list_opts) else - socket + {:ok, socket} end end @@ -529,7 +556,8 @@ defmodule Web.LiveTable do ArgumentError -> [] end - defp filter_to_form(filter, as) do + @doc false + def filter_to_form(filter, as) do # Note: we don't support nesting, :and or :where on the UI yet for {key, value} <- filter, into: %{} do {Atom.to_string(key), value} diff --git a/elixir/apps/web/test/web/live_table_test.exs b/elixir/apps/web/test/web/live_table_test.exs new file mode 100644 index 000000000..09f780f93 --- /dev/null +++ b/elixir/apps/web/test/web/live_table_test.exs @@ -0,0 +1,734 @@ +defmodule Web.LiveTableTest do + use Web.ConnCase, async: true + import Web.LiveTable + + describe "<.live_table /> component" do + setup do + assigns = %{ + id: "table-id", + filters: [], + filter: filter_to_form(%{}, "table-id"), + ordered_by: {:assoc, :name}, + metadata: %{ + previous_page_cursor: nil, + next_page_cursor: nil, + limit: 10, + count: 1 + }, + col: [ + %{ + label: "name", + field: {:assoc, :name}, + inner_block: fn _col, row -> + row + end + } + ], + rows: ["foo"] + } + + %{assigns: assigns} + end + + test "renders a data table", %{assigns: assigns} do + html = render_component(&live_table/1, assigns) + + assert html + |> Floki.find("table") + |> Floki.attribute("id") == ["table-id"] + + assert html + |> Floki.find("table thead") + |> Floki.attribute("id") == ["table-id-header"] + + assert html + |> Floki.find("th") + |> Floki.text() =~ "name" + + assert html + |> Floki.find("table tbody") + |> Floki.attribute("id") == ["table-id-rows"] + + assert html + |> Floki.find("td") + |> Floki.text() =~ "foo" + end + + test "renders fulltext search filter", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :search, + title: "Query", + type: {:string, :websearch} + } + ], + filter: filter_to_form(%{search: "foo"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + input = Floki.find(form, "input[type=text]") + assert Floki.attribute(input, "id") == ["table-id_search"] + assert Floki.attribute(input, "name") == ["table-id[search]"] + assert Floki.attribute(input, "placeholder") == ["Search by Query"] + assert Floki.attribute(input, "value") == ["foo"] + end + + test "renders email filter", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :email, + title: "Email", + type: {:string, :email} + } + ], + filter: filter_to_form(%{email: "foo@bar.com"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + input = Floki.find(form, "input[type=text]") + assert Floki.attribute(input, "id") == ["table-id_email"] + assert Floki.attribute(input, "name") == ["table-id[email]"] + assert Floki.attribute(input, "placeholder") == ["Search by Email"] + assert Floki.attribute(input, "value") == ["foo@bar.com"] + end + + test "renders UUID dropdown filter", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :id, + title: "ID", + type: {:string, :uuid}, + values: [ + {"group1", [{"One", "1"}]}, + {"group1", [{"Two", "2"}]}, + {nil, [{"Three", "3"}]} + ] + } + ], + filter: filter_to_form(%{id: "1"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + select = Floki.find(form, "select") + assert Floki.attribute(select, "id") == ["table-id_id"] + assert Floki.attribute(select, "name") == ["table-id[id]"] + + assert select |> List.first() |> elem(2) == [ + {"option", [{"value", ""}], ["For any ID"]}, + {"optgroup", [{"label", "group1"}], + [{"option", [{"selected", "selected"}, {"value", "1"}], ["One"]}]}, + {"optgroup", [{"label", "group1"}], [{"option", [{"value", "2"}], ["Two"]}]}, + {"option", [{"value", "3"}], ["Three"]} + ] + end + + test "renders radio buttons for select from up to 5 values", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :btn, + title: "Button", + type: :string, + values: [ + {"One", "1"}, + {"Two", "2"} + ] + } + ], + filter: filter_to_form(%{id: "1"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + radio = Floki.find(form, "input[type=radio]") + + assert Floki.attribute(radio, "id") == [ + "table-id-btn-__all__", + "table-id-btn-1", + "table-id-btn-2" + ] + + assert Floki.attribute(radio, "name") == [ + "_reset:table-id[btn]", + "table-id[btn]", + "table-id[btn]" + ] + + assert Floki.attribute(radio, "value") == [ + "true", + "1", + "2" + ] + end + + test "renders checkbox-like buttons for multi-select from up to 5 values", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :btn, + title: "Button", + type: {:list, :string}, + values: [ + {"One", "1"}, + {"Two", "2"} + ] + } + ], + filter: filter_to_form(%{id: "1"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + radio = Floki.find(form, "input[type=checkbox]") + + assert Floki.attribute(radio, "id") == [ + "table-id-btn-__all__", + "table-id-btn-1", + "table-id-btn-2" + ] + + assert Floki.attribute(radio, "name") == [ + "_reset:table-id[btn]", + "table-id[btn][]", + "table-id[btn][]" + ] + + assert Floki.attribute(radio, "value") == [ + "value", + "1", + "2" + ] + end + + test "renders value dropdown when there are more than 5 values", %{assigns: assigns} do + assigns = %{ + assigns + | filters: [ + %Domain.Repo.Filter{ + name: :select, + title: "Select", + type: :string, + values: [ + {"One", "1"}, + {"Two", "2"}, + {"Three", "3"}, + {"Four", "4"}, + {"Five", "5"}, + {"Six", "6"} + ] + } + ], + filter: filter_to_form(%{id: "1"}, "table-id") + } + + form = + render_component(&live_table/1, assigns) + |> Floki.find("form") + + assert Floki.attribute(form, "id") == ["table-id-filters"] + assert Floki.attribute(form, "phx-change") == ["filter"] + + input = Floki.find(form, "input[type=hidden]") + assert Floki.attribute(input, "name") == ["table_id"] + assert Floki.attribute(input, "value") == ["table-id"] + + select = Floki.find(form, "select") + assert Floki.attribute(select, "id") == ["table-id_select"] + assert Floki.attribute(select, "name") == ["table-id[select]"] + + assert select |> List.first() |> elem(2) == [ + {"option", [{"value", ""}], ["For any Select"]}, + {"optgroup", [{"label", "Select"}], + [ + {"option", [{"value", "1"}], ["One"]}, + {"option", [{"value", "2"}], ["Two"]}, + {"option", [{"value", "3"}], ["Three"]}, + {"option", [{"value", "4"}], ["Four"]}, + {"option", [{"value", "5"}], ["Five"]}, + {"option", [{"value", "6"}], ["Six"]} + ]} + ] + end + + test "renders ordering buttons", %{assigns: assigns} do + # default order when it's unset + html = render_component(&live_table/1, assigns) + order_button = Floki.find(html, "th button") + assert Floki.attribute(order_button, "phx-click") == ["order_by"] + assert Floki.attribute(order_button, "phx-value-table_id") == ["table-id"] + assert Floki.attribute(order_button, "phx-value-order_by") == ["assoc:asc:name"] + + # current order if it's set + assigns = %{assigns | ordered_by: {:assoc, :desc, :name}} + html = render_component(&live_table/1, assigns) + order_button = Floki.find(html, "th button") + assert Floki.attribute(order_button, "phx-value-order_by") == ["assoc:desc:name"] + end + + test "renders page size and total count", %{assigns: assigns} do + assert render_component(&live_table/1, assigns) + |> Floki.find("nav > span") + |> Floki.text() + |> String.replace(~r/[\s]+/, " ") =~ "Showing 1 of 1" + + assert render_component(&live_table/1, %{ + assigns + | metadata: %{assigns.metadata | count: 10, limit: 100} + }) + |> Floki.find("nav > span") + |> Floki.text() + |> String.replace(~r/[\s]+/, " ") =~ "Showing 10 of 10" + + assert render_component(&live_table/1, %{ + assigns + | metadata: %{assigns.metadata | count: 100, limit: 10} + }) + |> Floki.find("nav > span") + |> Floki.text() + |> String.replace(~r/[\s]+/, " ") =~ "Showing 10 of 100" + end + + test "renders pagination buttons", %{assigns: assigns} do + html = render_component(&live_table/1, assigns) + + assert html + |> Floki.find("nav button") + |> Floki.attribute("disabled") == ["disabled", "disabled"] + + assigns = %{assigns | metadata: %{assigns.metadata | next_page_cursor: "next_cursor"}} + html = render_component(&live_table/1, assigns) + + assert html + |> Floki.find("nav button") + |> Floki.attribute("disabled") == ["disabled"] + + enabled_button = Floki.find(html, "nav button:not([disabled])") + assert Floki.attribute(enabled_button, "phx-click") == ["paginate"] + assert Floki.attribute(enabled_button, "phx-value-cursor") == ["next_cursor"] + assert Floki.attribute(enabled_button, "phx-value-table_id") == ["table-id"] + + assigns = %{assigns | metadata: %{assigns.metadata | previous_page_cursor: "prev_cursor"}} + html = render_component(&live_table/1, assigns) + + assert html + |> Floki.find("nav button") + |> Floki.attribute("disabled") == [] + + enabled_button = Floki.find(html, "nav button:not([disabled])") + assert "prev_cursor" in Floki.attribute(enabled_button, "phx-value-cursor") + end + end + + describe "assign_live_table/3" do + setup do + subject = Fixtures.Auth.create_subject() + socket = %Phoenix.LiveView.Socket{assigns: %{subject: subject, __changed__: %{}}} + %{socket: socket} + end + + test "persists live table state in the socket", %{socket: socket} do + assert %{ + assigns: %{ + __changed__: %{ + live_table_ids: true, + callback_by_table_id: true, + sortable_fields_by_table_id: true, + filters_by_table_id: true, + enforced_filters_by_table_id: true, + limit_by_table_id: true + }, + live_table_ids: ["table-id"], + callback_by_table_id: %{"table-id" => _fun}, + sortable_fields_by_table_id: %{"table-id" => [actors: :name]}, + filters_by_table_id: %{"table-id" => []}, + enforced_filters_by_table_id: %{}, + limit_by_table_id: %{} + } + } = + assign_live_table(socket, "table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + assert %{ + assigns: %{ + __changed__: %{ + live_table_ids: true, + callback_by_table_id: true, + sortable_fields_by_table_id: true, + filters_by_table_id: true, + enforced_filters_by_table_id: true, + limit_by_table_id: true + }, + live_table_ids: ["table-id"], + callback_by_table_id: %{"table-id" => _fun}, + sortable_fields_by_table_id: %{"table-id" => [actors: :name]}, + filters_by_table_id: %{"table-id" => []}, + enforced_filters_by_table_id: %{"table-id" => [name: "foo"]}, + limit_by_table_id: %{"table-id" => 11} + } + } = + assign_live_table(socket, "table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + enforce_filters: [ + {:name, "foo"} + ], + hide_filters: [:email], + limit: 11, + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + end + end + + describe "reload_live_table!/2" do + test "reloads the live table" do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{subject: subject, __changed__: %{}} + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + assert %{ + private: %{list_opts: []} + } = reload_live_table!(socket, "table-id") + end + + test "reloads whole page on errors" do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{subject: subject, uri: "/current_uri", __changed__: %{}} + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn _socket, _list_opts -> {:error, :not_found} end + ) + + assert %{ + redirected: {:live, :redirect, %{kind: :push, to: "/current_uri"}} + } = reload_live_table!(socket, "table-id") + end + end + + describe "handle_live_tables_params/3" do + setup do + subject = Fixtures.Auth.create_subject() + + test_pid = self() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{subject: subject, __changed__: %{}} + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + send(test_pid, {:callback, socket, list_opts}) + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + %{socket: socket} + end + + test "assigns the live table data from callbacks", %{socket: socket} do + assert %{ + assigns: %{ + uri: "/actors" + }, + private: %{ + list_opts: [ + page: [limit: 25], + filter: [], + order_by: [] + ] + } + } = handle_live_tables_params(socket, %{}, "/actors") + + assert %{ + assigns: %{ + uri: "/actors" + }, + private: %{ + list_opts: [ + page: [cursor: "next_page", limit: 25], + filter: [{:name, "foo"}], + order_by: [{:actors, :asc, :name}] + ] + } + } = + handle_live_tables_params( + socket, + %{ + "table-id_cursor" => "next_page", + "table-id_filter" => %{"name" => "foo"}, + "table-id_order_by" => "actors:asc:name" + }, + "/actors" + ) + end + + test "does nothing when list opts are not changed", %{socket: socket} do + socket = handle_live_tables_params(socket, %{}, "/actors") + assert_receive {:callback, _socket, [page: [limit: 25], filter: [], order_by: []]} + + handle_live_tables_params(socket, %{}, "/actors") + refute_receive {:callback, _socket, _list_opts} + end + + test "raises if the callback returns an error" do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{subject: subject, uri: "/current_uri", __changed__: %{}} + } + + for {reason, exception} <- [ + {:not_found, Web.LiveErrors.NotFoundError}, + {:unauthorized, Web.LiveErrors.NotFoundError}, + {:invalid_cursor, Web.LiveErrors.InvalidRequestError}, + {{:unknown_filter, []}, Web.LiveErrors.InvalidRequestError}, + {{:invalid_type, []}, Web.LiveErrors.InvalidRequestError}, + {{:invalid_value, []}, Web.LiveErrors.InvalidRequestError} + ] do + socket = + assign_live_table(socket, "table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn _socket, _list_opts -> {:error, reason} end + ) + + assert_raise exception, fn -> + handle_live_tables_params(socket, %{}, "/foo") + end + end + end + end + + describe "handle_live_table_event/3 for pagination" do + setup do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{ + subject: subject, + uri: + "/actors?table-id_cursor=prev_page" <> + "&table-id_filter%5Bname%5D=buz" <> + "&table-id_order_by=actors%3Aasc%3Aname", + __changed__: %{} + } + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + %{socket: socket} + end + + test "updates query parameters with new cursor", %{socket: socket} do + assert handle_live_table_event( + "paginate", + %{"table_id" => "table-id", "cursor" => "very_next_page"}, + socket + ) + |> fetch_patched_query_params!() == %{ + "table-id_order_by" => "actors:asc:name", + "table-id_cursor" => "very_next_page", + "table-id_filter[name]" => "buz" + } + end + end + + describe "handle_live_table_event/3 for filtering" do + setup do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{ + subject: subject, + uri: + "/actors?table-id_cursor=next_page" <> + "&table-id_filter%5Bemail%5D=bar" <> + "&table-id_filter%5Bname%5D=buz" <> + "&table-id_order_by=actors%3Aasc%3Aname", + __changed__: %{} + } + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + %{socket: socket} + end + + test "resets the query parameters with filter value is set to all", %{socket: socket} do + assert handle_live_table_event( + "filter", + %{"table_id" => "table-id", "_target" => ["_reset:table-id", "name"]}, + socket + ) + |> fetch_patched_query_params!() == %{ + "table-id_filter[email]" => "bar", + "table-id_order_by" => "actors:asc:name" + } + end + + test "updates query parameters with new filter and resets the cursor", %{socket: socket} do + assert handle_live_table_event( + "filter", + %{"table_id" => "table-id", "table-id" => %{"name" => "foo"}}, + socket + ) + |> fetch_patched_query_params!() == %{ + "table-id_filter[name]" => "foo", + "table-id_order_by" => "actors:asc:name" + } + end + end + + describe "handle_live_table_event/3 for ordering" do + setup do + subject = Fixtures.Auth.create_subject() + + socket = + %Phoenix.LiveView.Socket{ + assigns: %{ + subject: subject, + uri: + "/actors?table-id_cursor=next_page&table-id_filter%5Bname%5D=bar&table-id_order_by=actors%3Aasc%3Aname", + __changed__: %{} + } + } + |> assign_live_table("table-id", + query_module: Actors.Actor.Query, + sortable_fields: [ + {:actors, :name} + ], + callback: fn socket, list_opts -> + {:ok, %{socket | private: %{list_opts: list_opts}}} + end + ) + + %{socket: socket} + end + + test "updates query parameters with reverse order and resets the cursor", %{socket: socket} do + assert handle_live_table_event( + "order_by", + %{"table_id" => "table-id", "order_by" => "actors:desc:name"}, + socket + ) + |> fetch_patched_query_params!() == %{ + "table-id_filter[name]" => "bar", + "table-id_order_by" => "actors:asc:name" + } + end + end + + defp fetch_patched_query_params!(socket) do + assert {:noreply, %{redirected: {:live, :patch, %{kind: :push, to: to}}}} = socket + uri = URI.parse(to) + URI.decode_query(uri.query) + end +end