From bd4b5581c81f7c04d3d554120207ba2f50563d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberts=20Gu=C4=BC=C4=81ns?= Date: Wed, 15 May 2024 21:10:36 +0300 Subject: [PATCH] #102 add `show_resources` option (#148) --- .formatter.exs | 1 + dev/resources/tickets/domain.ex | 7 ++ documentation/dsls/DSL:-AshAdmin.Domain.md | 1 + lib/ash_admin/actor_plug/plug.ex | 4 +- lib/ash_admin/components/top_nav.ex | 4 +- lib/ash_admin/domain.ex | 13 ++- lib/ash_admin/pages/page_live.ex | 2 +- lib/ash_admin/resource/resource.ex | 2 +- lib/ash_admin/show_resources_transformer.ex | 28 +++++++ test/ash_admin_test.exs | 87 +++++++++++++++++++++ test/support/domain.ex | 1 + test/support/resources/comment.ex | 15 ++++ 12 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 lib/ash_admin/show_resources_transformer.ex create mode 100644 test/support/resources/comment.ex diff --git a/.formatter.exs b/.formatter.exs index 838c9ec..05a826e 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -15,6 +15,7 @@ spark_locals_without_parens = [ resource_group_labels: 1, show?: 1, show_action: 1, + show_resources: 1, show_sensitive_fields: 1, table_columns: 1, type: 1, diff --git a/dev/resources/tickets/domain.ex b/dev/resources/tickets/domain.ex index 7dc93d8..5402b07 100644 --- a/dev/resources/tickets/domain.ex +++ b/dev/resources/tickets/domain.ex @@ -5,6 +5,13 @@ defmodule Demo.Tickets.Domain do admin do show? true + show_resources [ + Demo.Tickets.Customer, + Demo.Tickets.Representative, + Demo.Tickets.Ticket, + Demo.Tickets.Comment, + Demo.Tickets.Organization + ] end resources do diff --git a/documentation/dsls/DSL:-AshAdmin.Domain.md b/documentation/dsls/DSL:-AshAdmin.Domain.md index 2c895cf..a9587f4 100644 --- a/documentation/dsls/DSL:-AshAdmin.Domain.md +++ b/documentation/dsls/DSL:-AshAdmin.Domain.md @@ -20,6 +20,7 @@ Configure the admin dashboard for a given domain. |------|------|---------|------| | [`name`](#admin-name){: #admin-name } | `String.t` | | The name of the domain in the dashboard. Will be derived if not set. | | [`show?`](#admin-show?){: #admin-show? } | `boolean` | `false` | Whether or not this domain and its resources should be included in the admin dashboard. | +| [`show_resources`](#admin-show_resources){: #admin-show_resources } | `atom \| list(atom)` | `:*` | List of resources that should be included in the admin dashboard | | [`default_resource_page`](#admin-default_resource_page){: #admin-default_resource_page } | `:schema \| :primary_read` | `:schema` | Set the default page for the resource to be the primary read action or the resource schema. Schema is the default for backwards compatibility, if a resource doesn't have a primary read action it will fallback to the schema view. | | [`resource_group_labels`](#admin-resource_group_labels){: #admin-resource_group_labels } | `keyword` | `[]` | Humanized names for each resource group to appear in the admin area. These will be used as labels in the top navigation dropdown. If a key for a group does not appear in this mapping, the label will not be rendered. | diff --git a/lib/ash_admin/actor_plug/plug.ex b/lib/ash_admin/actor_plug/plug.ex index e71f2a2..285eaef 100644 --- a/lib/ash_admin/actor_plug/plug.ex +++ b/lib/ash_admin/actor_plug/plug.ex @@ -85,7 +85,7 @@ defmodule AshAdmin.ActorPlug.Plug do defp actor_resources(domains) do for domain <- domains, - resource <- Ash.Domain.Info.resources(domain), + resource <- AshAdmin.Domain.show_resources(domain), AshAdmin.Helpers.primary_action(resource, :read) && AshAdmin.Resource.actor?(resource), do: {domain, resource} end @@ -120,7 +120,7 @@ defmodule AshAdmin.ActorPlug.Plug do resource = if domain do domain - |> Ash.Domain.Info.resources() + |> AshAdmin.Domain.show_resources() |> Enum.find(&(AshAdmin.Resource.name(&1) == resource)) end diff --git a/lib/ash_admin/components/top_nav.ex b/lib/ash_admin/components/top_nav.ex index 3be273c..c473104 100644 --- a/lib/ash_admin/components/top_nav.ex +++ b/lib/ash_admin/components/top_nav.ex @@ -143,7 +143,7 @@ defmodule AshAdmin.Components.TopNav do end defp dropdown_groups(prefix, current_resource, domain) do - for resource <- Ash.Domain.Info.resources(domain) do + for resource <- AshAdmin.Domain.show_resources(domain) do %{ text: AshAdmin.Resource.name(resource), to: @@ -180,7 +180,7 @@ defmodule AshAdmin.Components.TopNav do defp show_tenant_form?(domains) do Enum.any?(domains, fn domain -> domain - |> Ash.Domain.Info.resources() + |> AshAdmin.Domain.show_resources() |> Enum.any?(fn resource -> Ash.Resource.Info.multitenancy_strategy(resource) end) diff --git a/lib/ash_admin/domain.ex b/lib/ash_admin/domain.ex index 1e1ba3b..39b34f6 100644 --- a/lib/ash_admin/domain.ex +++ b/lib/ash_admin/domain.ex @@ -13,6 +13,11 @@ defmodule AshAdmin.Domain do doc: "Whether or not this domain and its resources should be included in the admin dashboard." ], + show_resources: [ + type: {:wrap_list, :atom}, + default: :*, + doc: "List of resources that should be included in the admin dashboard" + ], default_resource_page: [ type: {:in, [:schema, :primary_read]}, default: :schema, @@ -28,7 +33,9 @@ defmodule AshAdmin.Domain do ] } - use Spark.Dsl.Extension, sections: [@admin] + use Spark.Dsl.Extension, + sections: [@admin], + transformers: [AshAdmin.ShowResourcesTransformer] @moduledoc """ A domain extension to alter the behavior of a domain in the admin UI. @@ -42,6 +49,10 @@ defmodule AshAdmin.Domain do Spark.Dsl.Extension.get_opt(domain, [:admin], :show?, false, true) end + def show_resources(domain) do + Spark.Dsl.Extension.get_opt(domain, [:admin], :show_resources, [], true) + end + def default_resource_page(domain) do Spark.Dsl.Extension.get_opt(domain, [:admin], :default_resource_page, :schema, true) end diff --git a/lib/ash_admin/pages/page_live.ex b/lib/ash_admin/pages/page_live.ex index be1da9f..9188350 100644 --- a/lib/ash_admin/pages/page_live.ex +++ b/lib/ash_admin/pages/page_live.ex @@ -124,7 +124,7 @@ defmodule AshAdmin.PageLive do defp assign_resource(socket, resource) do if socket.assigns.domain do - resources = Ash.Domain.Info.resources(socket.assigns.domain) + resources = AshAdmin.Domain.show_resources(socket.assigns.domain) resource = Enum.find(resources, fn domain_resources -> diff --git a/lib/ash_admin/resource/resource.ex b/lib/ash_admin/resource/resource.ex index 1d5c1bb..1e82b24 100644 --- a/lib/ash_admin/resource/resource.ex +++ b/lib/ash_admin/resource/resource.ex @@ -194,7 +194,7 @@ defmodule AshAdmin.Resource do defp find_polymorphic_tables(resource, domains) do domains - |> Enum.flat_map(&Ash.Domain.Info.resources/1) + |> Enum.flat_map(&AshAdmin.Domain.show_resources/1) |> Enum.flat_map(&Ash.Resource.Info.relationships/1) |> Enum.filter(&(&1.destination == resource)) |> Enum.map(& &1.context[:data_layer][:table]) diff --git a/lib/ash_admin/show_resources_transformer.ex b/lib/ash_admin/show_resources_transformer.ex new file mode 100644 index 0000000..74f3460 --- /dev/null +++ b/lib/ash_admin/show_resources_transformer.ex @@ -0,0 +1,28 @@ +defmodule AshAdmin.ShowResourcesTransformer do + use Spark.Dsl.Transformer + + def transform(dsl) do + module = Spark.Dsl.Transformer.get_persisted(dsl, :module) + all_resources = Ash.Domain.Info.resources(dsl) + + resources = + case AshAdmin.Domain.show_resources(dsl) do + [:*] -> + all_resources + + resources -> + case Enum.find(resources, &(&1 not in all_resources)) do + nil -> + resources + + bad_resource -> + raise Spark.Error.DslError, + module: module, + path: [:admin, :show_resources], + message: "#{inspect(bad_resource)} is not a valid resource in #{inspect(module)}" + end + end + + {:ok, Spark.Dsl.Transformer.set_option(dsl, [:admin], :show_resources, resources)} + end +end diff --git a/test/ash_admin_test.exs b/test/ash_admin_test.exs index 92b1150..858ae24 100644 --- a/test/ash_admin_test.exs +++ b/test/ash_admin_test.exs @@ -1,4 +1,91 @@ defmodule AshAdmin.Test.AshAdminTest do @moduledoc false use ExUnit.Case, async: true + + test "all resources are shown by default", _ do + defmodule Domain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + end + + resources do + resource AshAdmin.Test.Post + resource AshAdmin.Test.Comment + end + end + + assert AshAdmin.Domain.show_resources(Domain) === [ + AshAdmin.Test.Post, + AshAdmin.Test.Comment + ] + end + + test "all resources are shown when :* option is selected", _ do + defmodule Domain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + show_resources :* + end + + resources do + resource AshAdmin.Test.Post + resource AshAdmin.Test.Comment + end + end + + assert AshAdmin.Domain.show_resources(Domain) === [ + AshAdmin.Test.Post, + AshAdmin.Test.Comment + ] + end + + test "selected resources are shown", _ do + defmodule Domain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + show_resources AshAdmin.Test.Post + end + + resources do + resource AshAdmin.Test.Post + resource AshAdmin.Test.Comment + end + end + + assert AshAdmin.Domain.show_resources(Domain) === [ + AshAdmin.Test.Post + ] + end + + test "if shown resrouces option not eixsting resource providede error", _ do + assert_raise(Spark.Error.DslError, "[AshAdmin.Test.AshAdminTest.Domain]\n admin -> show_resources:\n SomeRandom is not a valid resource in AshAdmin.Test.AshAdminTest.Domain", fn -> + defmodule Domain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + show_resources [AshAdmin.Test.Post, SomeRandom] + end + + resources do + resource AshAdmin.Test.Post + resource AshAdmin.Test.Comment + end + end + end) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index ae1babe..ff9ab9a 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -9,5 +9,6 @@ defmodule AshAdmin.Test.Domain do resources do resource(AshAdmin.Test.Post) + resource(AshAdmin.Test.Comment) end end diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex new file mode 100644 index 0000000..55db028 --- /dev/null +++ b/test/support/resources/comment.ex @@ -0,0 +1,15 @@ +defmodule AshAdmin.Test.Comment do + @moduledoc false + use Ash.Resource, + domain: AshAdmin.Test.Domain, + data_layer: Ash.DataLayer.Ets + + attributes do + uuid_primary_key(:id) + + attribute :body, :string do + allow_nil?(false) + public?(true) + end + end +end