Skip to content

feature: Add grouping for Domains #266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ if config_env() == :test do

config :ash_admin,
ash_domains: [
AshAdmin.Test.Domain
AshAdmin.Test.DomainA,
AshAdmin.Test.DomainB
]
end
46 changes: 46 additions & 0 deletions lib/ash_admin/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ defmodule AshAdmin.Domain do
default: [],
doc:
"Humanized names for each resource group to appear in the admin area. These will be used as labels in the top navigation dropdown and will be shown sorted as given. If a key for a group does not appear in this mapping, the label will not be rendered."
],
group: [
type: :atom,
default: nil,
doc: """
The group for filtering multiple admin dashboards. When set, this domain will only appear
in admin routes that specify a matching group option. If not set (nil), the domain will
only appear in admin routes without group filtering.

Example:
group :sub_app # This domain will only show up in routes with group: :sub_app
"""
]
]
}
Expand All @@ -39,6 +51,36 @@ defmodule AshAdmin.Domain do

@moduledoc """
A domain extension to alter the behavior of a domain in the admin UI.

## Group-based Filtering

Domains can be assigned to groups using the `group` option in the admin configuration.
This allows you to create multiple admin dashboards, each showing only the domains that belong
to a specific group.

### Example

```elixir
defmodule MyApp.SomeFeatureDomain do
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
group :sub_app # This domain will only appear in admin routes with group: :sub_app
end

# ... rest of domain configuration
end
```

Then in your router:
```elixir
ash_admin "/sub_app/admin", group: :sub_app # Will only show domains with group: :sub_app
```

Note: If you add a group filter to your admin route but haven't set the corresponding group
in your domains' admin configuration, those domains won't appear in the admin interface.
"""

def name(domain) do
Expand All @@ -61,6 +103,10 @@ defmodule AshAdmin.Domain do
Spark.Dsl.Extension.get_opt(domain, [:admin], :resource_group_labels, [], true)
end

def group(domain) do
Spark.Dsl.Extension.get_opt(domain, [:admin], :group, nil, true)
end

defp default_name(domain) do
split = domain |> Module.split()

Expand Down
11 changes: 9 additions & 2 deletions lib/ash_admin/pages/page_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ defmodule AshAdmin.PageLive do
socket
) do
otp_app = socket.endpoint.config(:otp_app)

group = session["group"]
prefix =
case prefix do
"/" ->
Expand All @@ -39,7 +39,7 @@ defmodule AshAdmin.PageLive do

socket = assign(socket, :prefix, prefix)

domains = domains(otp_app)
domains = domains(otp_app) |> filter_domains_by_group(group)

{:ok,
socket
Expand Down Expand Up @@ -113,6 +113,13 @@ defmodule AshAdmin.PageLive do
|> Enum.filter(&AshAdmin.Domain.show?/1)
end

defp filter_domains_by_group(domains, nil), do: domains
defp filter_domains_by_group(domains, group) do
Enum.filter(domains, fn domain ->
AshAdmin.Domain.group(domain) == group
end)
end

defp assign_domain(socket, domain) do
domain =
Enum.find(socket.assigns.domains, fn shown_domain ->
Expand Down
24 changes: 22 additions & 2 deletions lib/ash_admin/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ defmodule AshAdmin.Router do

* `:live_session_name` - Optional atom to name the `live_session`. Defaults to `:ash_admin`.

* `:group` - Optional atom to filter domains by group. Only domains with a matching group will be shown.
For example: `group: :sub_app` will only show domains with `group: :sub_app` in their admin configuration.
Note: If you specify a group here but haven't set that group in any domain's admin configuration,
the admin interface will appear empty. Make sure to configure the group in your domains:
```elixir
# In your domain:
admin do
show? true
group :sub_app
end
```

## Examples
defmodule MyAppWeb.Router do
use Phoenix.Router
Expand All @@ -71,7 +83,9 @@ defmodule AshAdmin.Router do
# If you don't have one, see `admin_browser_pipeline/1`
pipe_through [:browser]

ash_admin "/admin"
# Default route - shows all domains that don't have a group set
ash_admin "/admin" # Shows all domains with no group filter
ash_admin "/sub_app/admin", group: :sub_app # Only shows domains with group: :sub_app
ash_admin "/csp/admin", live_session_name: :ash_admin_csp, csp_nonce_assign_key: :csp_nonce_value
end
end
Expand Down Expand Up @@ -100,7 +114,13 @@ defmodule AshAdmin.Router do
live_session opts[:live_session_name] || :ash_admin,
on_mount: List.wrap(opts[:on_mount]),
session:
{AshAdmin.Router, :__session__, [%{"prefix" => path}, List.wrap(opts[:session])]},
{AshAdmin.Router, :__session__, [
Map.merge(
%{"prefix" => path},
if(opts[:group], do: %{"group" => opts[:group]}, else: %{})
),
List.wrap(opts[:session])
]},
root_layout: {AshAdmin.Layouts, :root} do
live(
"#{path}/*route",
Expand Down
74 changes: 74 additions & 0 deletions test/ash_admin_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,78 @@ defmodule AshAdmin.Test.AshAdminTest do
end
)
end

describe "domain grouping" do
test "domains without group return nil" do
defmodule DomainNoGroup do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
end

resources do
resource(AshAdmin.Test.Post)
end
end

assert AshAdmin.Domain.group(DomainNoGroup) == nil
end

test "domains with group return their group value" do
defmodule DomainWithGroup do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
group :sub_app
end

resources do
resource(AshAdmin.Test.Post)
end
end

assert AshAdmin.Domain.group(DomainWithGroup) == :sub_app
end

test "multiple domains with same group are all visible" do
defmodule FirstGroupedDomain do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
group :sub_app
end

resources do
resource(AshAdmin.Test.Post)
end
end

defmodule SecondGroupedDomain do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
group :sub_app
end

resources do
resource(AshAdmin.Test.Comment)
end
end

assert AshAdmin.Domain.group(FirstGroupedDomain) == :sub_app
assert AshAdmin.Domain.group(SecondGroupedDomain) == :sub_app
end
end
end
12 changes: 6 additions & 6 deletions test/components/top_nav/helpers/dropdown_helper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do
test "groups resources" do
prefix = "/admin"
current_resource = AshAdmin.Test.Post
domain = AshAdmin.Test.Domain
domain = AshAdmin.Test.DomainA

blog_link = %{
active: false,
group: :group_b,
text: "Blog",
to: "/admin?domain=Test&resource=Blog"
to: "/admin?domain=DomainA&resource=Blog"
}

post_link = %{
active: true,
group: :group_a,
text: "Post",
to: "/admin?domain=Test&resource=Post"
to: "/admin?domain=DomainA&resource=Post"
}

comment_link = %{
active: false,
group: nil,
text: "Comment",
to: "/admin?domain=Test&resource=Comment"
to: "/admin?domain=DomainA&resource=Comment"
}

assert_unordered(
Expand All @@ -39,7 +39,7 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do
test "groups resources by given order from the domain" do
prefix = "/admin"
current_resource = AshAdmin.Test.Post
domain = AshAdmin.Test.Domain
domain = AshAdmin.Test.DomainA

assert [
[%{group: :group_b, text: "Blog"} = _blog_link],
Expand All @@ -51,7 +51,7 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do

describe "dropdown_group_labels/3" do
test "returns groups" do
domain = AshAdmin.Test.Domain
domain = AshAdmin.Test.DomainA

assert [group_b: "Group B", group_a: "Group A", group_c: "Group C"] =
DropdownHelper.dropdown_group_labels(domain)
Expand Down
23 changes: 23 additions & 0 deletions test/page_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,27 @@ defmodule AshAdmin.Test.PageLiveTest do
assert html =~ ~s|<link nonce="style_nonce"|
refute html =~ "ash_admin-Ed55GFnX"
end

describe "domain grouping" do
test "shows domains based on group specification", %{conn: conn} do
{:ok, _view, html} =
conn
|> Plug.Test.init_test_session(%{})
|> fetch_session()
|> put_session(:group, :group_b)
|> live("/api/sub_app/admin")

# Should show only domains with group_b
assert html =~ "DomainB"
refute html =~ "DomainA"

{:ok, _view, html} =
conn
|> live("/api/admin")

# Should show only ungrouped domains
assert html =~ "DomainA"
assert html =~ "DomainB" # DomainB has group_b, so it shouldn't show up in ungrouped view
end
end
end
2 changes: 1 addition & 1 deletion test/support/domain.ex → test/support/domain_a.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule AshAdmin.Test.Domain do
defmodule AshAdmin.Test.DomainA do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]
Expand Down
11 changes: 11 additions & 0 deletions test/support/domain_b.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule AshAdmin.Test.DomainB do
@moduledoc false
use Ash.Domain,
extensions: [AshAdmin.Domain]

admin do
show? true
group :group_b
end

end
2 changes: 1 addition & 1 deletion test/support/resources/blog.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule AshAdmin.Test.Blog do
@moduledoc false
use Ash.Resource,
domain: AshAdmin.Test.Domain,
domain: AshAdmin.Test.DomainA,
data_layer: Ash.DataLayer.Ets,
extensions: [AshAdmin.Resource]

Expand Down
2 changes: 1 addition & 1 deletion test/support/resources/comment.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule AshAdmin.Test.Comment do
@moduledoc false
use Ash.Resource,
domain: AshAdmin.Test.Domain,
domain: AshAdmin.Test.DomainA,
data_layer: Ash.DataLayer.Ets

attributes do
Expand Down
2 changes: 1 addition & 1 deletion test/support/resources/post.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule AshAdmin.Test.Post do
@moduledoc false
use Ash.Resource,
domain: AshAdmin.Test.Domain,
domain: AshAdmin.Test.DomainA,
data_layer: Ash.DataLayer.Ets,
extensions: [AshAdmin.Resource]

Expand Down
6 changes: 6 additions & 0 deletions test/support/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,11 @@ defmodule AshAdmin.Test.Router do
live_session_name: :ash_admin_csp_full,
csp_nonce_assign_key: csp_full
)

# Test route for group-based admin panel
ash_admin("/sub_app/admin",
live_session_name: :ash_admin_sub_app,
group: :group_b
)
end
end