Skip to content

Commit 5b9fa64

Browse files
committed
feature: Add grouping for Domains
- Allow a domain to have a :group. - When defining ash_admin routes, allow to filter on a domain group. - Test: For the ash_admin_test.exs to ensure the group is accessible. - Test: For verifying the behaviour on page_live. - Test/refactor: DomainA and DomainB, to easier test one against the other.
1 parent 452cfb6 commit 5b9fa64

File tree

13 files changed

+203
-15
lines changed

13 files changed

+203
-15
lines changed

config/config.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ if config_env() == :test do
7777

7878
config :ash_admin,
7979
ash_domains: [
80-
AshAdmin.Test.Domain
80+
AshAdmin.Test.DomainA,
81+
AshAdmin.Test.DomainB
8182
]
8283
end

lib/ash_admin/domain.ex

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ defmodule AshAdmin.Domain do
2929
default: [],
3030
doc:
3131
"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."
32+
],
33+
group: [
34+
type: :atom,
35+
default: nil,
36+
doc: """
37+
The group for filtering multiple admin dashboards. When set, this domain will only appear
38+
in admin routes that specify a matching group option. If not set (nil), the domain will
39+
only appear in admin routes without group filtering.
40+
41+
Example:
42+
group :sub_app # This domain will only show up in routes with group: :sub_app
43+
"""
3244
]
3345
]
3446
}
@@ -39,6 +51,36 @@ defmodule AshAdmin.Domain do
3951

4052
@moduledoc """
4153
A domain extension to alter the behavior of a domain in the admin UI.
54+
55+
## Group-based Filtering
56+
57+
Domains can be assigned to groups using the `group` option in the admin configuration.
58+
This allows you to create multiple admin dashboards, each showing only the domains that belong
59+
to a specific group.
60+
61+
### Example
62+
63+
```elixir
64+
defmodule MyApp.SomeFeatureDomain do
65+
use Ash.Domain,
66+
extensions: [AshAdmin.Domain]
67+
68+
admin do
69+
show? true
70+
group :sub_app # This domain will only appear in admin routes with group: :sub_app
71+
end
72+
73+
# ... rest of domain configuration
74+
end
75+
```
76+
77+
Then in your router:
78+
```elixir
79+
ash_admin "/sub_app/admin", group: :sub_app # Will only show domains with group: :sub_app
80+
```
81+
82+
Note: If you add a group filter to your admin route but haven't set the corresponding group
83+
in your domains' admin configuration, those domains won't appear in the admin interface.
4284
"""
4385

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

106+
def group(domain) do
107+
Spark.Dsl.Extension.get_opt(domain, [:admin], :group, nil, true)
108+
end
109+
64110
defp default_name(domain) do
65111
split = domain |> Module.split()
66112

lib/ash_admin/pages/page_live.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule AshAdmin.PageLive do
2525
socket
2626
) do
2727
otp_app = socket.endpoint.config(:otp_app)
28-
28+
group = session["group"]
2929
prefix =
3030
case prefix do
3131
"/" ->
@@ -39,7 +39,7 @@ defmodule AshAdmin.PageLive do
3939

4040
socket = assign(socket, :prefix, prefix)
4141

42-
domains = domains(otp_app)
42+
domains = domains(otp_app) |> filter_domains_by_group(group)
4343

4444
{:ok,
4545
socket
@@ -113,6 +113,13 @@ defmodule AshAdmin.PageLive do
113113
|> Enum.filter(&AshAdmin.Domain.show?/1)
114114
end
115115

116+
defp filter_domains_by_group(domains, nil), do: domains
117+
defp filter_domains_by_group(domains, group) do
118+
Enum.filter(domains, fn domain ->
119+
AshAdmin.Domain.group(domain) == group
120+
end)
121+
end
122+
116123
defp assign_domain(socket, domain) do
117124
domain =
118125
Enum.find(socket.assigns.domains, fn shown_domain ->

lib/ash_admin/router.ex

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ defmodule AshAdmin.Router do
6060
6161
* `:live_session_name` - Optional atom to name the `live_session`. Defaults to `:ash_admin`.
6262
63+
* `:group` - Optional atom to filter domains by group. Only domains with a matching group will be shown.
64+
For example: `group: :sub_app` will only show domains with `group: :sub_app` in their admin configuration.
65+
Note: If you specify a group here but haven't set that group in any domain's admin configuration,
66+
the admin interface will appear empty. Make sure to configure the group in your domains:
67+
```elixir
68+
# In your domain:
69+
admin do
70+
show? true
71+
group :sub_app
72+
end
73+
```
74+
6375
## Examples
6476
defmodule MyAppWeb.Router do
6577
use Phoenix.Router
@@ -71,7 +83,9 @@ defmodule AshAdmin.Router do
7183
# If you don't have one, see `admin_browser_pipeline/1`
7284
pipe_through [:browser]
7385
74-
ash_admin "/admin"
86+
# Default route - shows all domains that don't have a group set
87+
ash_admin "/admin" # Shows all domains with no group filter
88+
ash_admin "/sub_app/admin", group: :sub_app # Only shows domains with group: :sub_app
7589
ash_admin "/csp/admin", live_session_name: :ash_admin_csp, csp_nonce_assign_key: :csp_nonce_value
7690
end
7791
end
@@ -100,7 +114,13 @@ defmodule AshAdmin.Router do
100114
live_session opts[:live_session_name] || :ash_admin,
101115
on_mount: List.wrap(opts[:on_mount]),
102116
session:
103-
{AshAdmin.Router, :__session__, [%{"prefix" => path}, List.wrap(opts[:session])]},
117+
{AshAdmin.Router, :__session__, [
118+
Map.merge(
119+
%{"prefix" => path},
120+
if(opts[:group], do: %{"group" => opts[:group]}, else: %{})
121+
),
122+
List.wrap(opts[:session])
123+
]},
104124
root_layout: {AshAdmin.Layouts, :root} do
105125
live(
106126
"#{path}/*route",

test/ash_admin_test.exs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,78 @@ defmodule AshAdmin.Test.AshAdminTest do
9292
end
9393
)
9494
end
95+
96+
describe "domain grouping" do
97+
test "domains without group return nil" do
98+
defmodule DomainNoGroup do
99+
@moduledoc false
100+
use Ash.Domain,
101+
extensions: [AshAdmin.Domain]
102+
103+
admin do
104+
show? true
105+
end
106+
107+
resources do
108+
resource(AshAdmin.Test.Post)
109+
end
110+
end
111+
112+
assert AshAdmin.Domain.group(DomainNoGroup) == nil
113+
end
114+
115+
test "domains with group return their group value" do
116+
defmodule DomainWithGroup do
117+
@moduledoc false
118+
use Ash.Domain,
119+
extensions: [AshAdmin.Domain]
120+
121+
admin do
122+
show? true
123+
group :sub_app
124+
end
125+
126+
resources do
127+
resource(AshAdmin.Test.Post)
128+
end
129+
end
130+
131+
assert AshAdmin.Domain.group(DomainWithGroup) == :sub_app
132+
end
133+
134+
test "multiple domains with same group are all visible" do
135+
defmodule FirstGroupedDomain do
136+
@moduledoc false
137+
use Ash.Domain,
138+
extensions: [AshAdmin.Domain]
139+
140+
admin do
141+
show? true
142+
group :sub_app
143+
end
144+
145+
resources do
146+
resource(AshAdmin.Test.Post)
147+
end
148+
end
149+
150+
defmodule SecondGroupedDomain do
151+
@moduledoc false
152+
use Ash.Domain,
153+
extensions: [AshAdmin.Domain]
154+
155+
admin do
156+
show? true
157+
group :sub_app
158+
end
159+
160+
resources do
161+
resource(AshAdmin.Test.Comment)
162+
end
163+
end
164+
165+
assert AshAdmin.Domain.group(FirstGroupedDomain) == :sub_app
166+
assert AshAdmin.Domain.group(SecondGroupedDomain) == :sub_app
167+
end
168+
end
95169
end

test/components/top_nav/helpers/dropdown_helper_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do
77
test "groups resources" do
88
prefix = "/admin"
99
current_resource = AshAdmin.Test.Post
10-
domain = AshAdmin.Test.Domain
10+
domain = AshAdmin.Test.DomainA
1111

1212
blog_link = %{
1313
active: false,
1414
group: :group_b,
1515
text: "Blog",
16-
to: "/admin?domain=Test&resource=Blog"
16+
to: "/admin?domain=DomainA&resource=Blog"
1717
}
1818

1919
post_link = %{
2020
active: true,
2121
group: :group_a,
2222
text: "Post",
23-
to: "/admin?domain=Test&resource=Post"
23+
to: "/admin?domain=DomainA&resource=Post"
2424
}
2525

2626
comment_link = %{
2727
active: false,
2828
group: nil,
2929
text: "Comment",
30-
to: "/admin?domain=Test&resource=Comment"
30+
to: "/admin?domain=DomainA&resource=Comment"
3131
}
3232

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

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

5252
describe "dropdown_group_labels/3" do
5353
test "returns groups" do
54-
domain = AshAdmin.Test.Domain
54+
domain = AshAdmin.Test.DomainA
5555

5656
assert [group_b: "Group B", group_a: "Group A", group_c: "Group C"] =
5757
DropdownHelper.dropdown_group_labels(domain)

test/page_live_test.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,27 @@ defmodule AshAdmin.Test.PageLiveTest do
5959
assert html =~ ~s|<link nonce="style_nonce"|
6060
refute html =~ "ash_admin-Ed55GFnX"
6161
end
62+
63+
describe "domain grouping" do
64+
test "shows domains based on group specification", %{conn: conn} do
65+
{:ok, _view, html} =
66+
conn
67+
|> Plug.Test.init_test_session(%{})
68+
|> fetch_session()
69+
|> put_session(:group, :group_b)
70+
|> live("/api/sub_app/admin")
71+
72+
# Should show only domains with group_b
73+
assert html =~ "DomainB"
74+
refute html =~ "DomainA"
75+
76+
{:ok, _view, html} =
77+
conn
78+
|> live("/api/admin")
79+
80+
# Should show only ungrouped domains
81+
assert html =~ "DomainA"
82+
assert html =~ "DomainB" # DomainB has group_b, so it shouldn't show up in ungrouped view
83+
end
84+
end
6285
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
defmodule AshAdmin.Test.Domain do
1+
defmodule AshAdmin.Test.DomainA do
22
@moduledoc false
33
use Ash.Domain,
44
extensions: [AshAdmin.Domain]

test/support/domain_b.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule AshAdmin.Test.DomainB do
2+
@moduledoc false
3+
use Ash.Domain,
4+
extensions: [AshAdmin.Domain]
5+
6+
admin do
7+
show? true
8+
group :group_b
9+
end
10+
11+
end

test/support/resources/blog.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule AshAdmin.Test.Blog do
22
@moduledoc false
33
use Ash.Resource,
4-
domain: AshAdmin.Test.Domain,
4+
domain: AshAdmin.Test.DomainA,
55
data_layer: Ash.DataLayer.Ets,
66
extensions: [AshAdmin.Resource]
77

0 commit comments

Comments
 (0)