Skip to content
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ Extra options:

App config can be used to set a global default to apply to all resources unless overridden in their individual config, or the LiveAdmin instance.

Extra options:
When set at the application level, resource-level keys are read at **compile time**, and must be configured in `config/config.exs` (or another compile-time config file). Setting any of them in `config/runtime.exs` (or otherwise at runtime) will raise on boot.

Compile-time keys: `components`, `query_with`, `render_with`, `delete_with`, `create_with`, `update_with`, `validate_with`, `label_with`, `title_with`, `hidden_fields`, `immutable_fields`, `actions`, `tasks`

Runtime keys:

* `ecto_repo` - module used to execute queries (Required)
* `session_store` - a module implementing the `LiveAdmin.Session.Store` behavior, used to persist session data (default: LiveAdmin.Session.Agent)
* `css_overrides` - a binary or MFA identifying a function that returns CSS to be appended to app css
* `gettext_backend` - a module implementing the [Gettext API](https://hexdocs.pm/gettext/Gettext.html#module-gettext-api) that will be used for translations
Expand Down
7 changes: 6 additions & 1 deletion README.md.eex
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ Extra options:

App config can be used to set a global default to apply to all resources unless overridden in their individual config, or the LiveAdmin instance.

Extra options:
When set at the application level, resource-level keys are read at **compile time**, and must be configured in `config/config.exs` (or another compile-time config file). Setting any of them in `config/runtime.exs` (or otherwise at runtime) will raise on boot.

Compile-time keys: `components`, `query_with`, `render_with`, `delete_with`, `create_with`, `update_with`, `validate_with`, `label_with`, `title_with`, `hidden_fields`, `immutable_fields`, `actions`, `tasks`

Runtime keys:

* `ecto_repo` - module used to execute queries (Required)
* `session_store` - a module implementing the `LiveAdmin.Session.Store` behavior, used to persist session data (default: LiveAdmin.Session.Agent)
* `css_overrides` - a binary or MFA identifying a function that returns CSS to be appended to app css
* `gettext_backend` - a module implementing the [Gettext API](https://hexdocs.pm/gettext/Gettext.html#module-gettext-api) that will be used for translations
Expand Down
55 changes: 55 additions & 0 deletions lib/application.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
defmodule LiveAdmin.Application do
use Application

@compile_time_app_keys [
:components,
:query_with,
:render_with,
:delete_with,
:create_with,
:update_with,
:validate_with,
:label_with,
:title_with,
:hidden_fields,
:immutable_fields,
:actions,
:tasks
]

@compile_time_app_config %{
components: Application.compile_env(:live_admin, :components),
query_with: Application.compile_env(:live_admin, :query_with),
render_with: Application.compile_env(:live_admin, :render_with),
delete_with: Application.compile_env(:live_admin, :delete_with),
create_with: Application.compile_env(:live_admin, :create_with),
update_with: Application.compile_env(:live_admin, :update_with),
validate_with: Application.compile_env(:live_admin, :validate_with),
label_with: Application.compile_env(:live_admin, :label_with),
title_with: Application.compile_env(:live_admin, :title_with),
hidden_fields: Application.compile_env(:live_admin, :hidden_fields),
immutable_fields: Application.compile_env(:live_admin, :immutable_fields),
actions: Application.compile_env(:live_admin, :actions),
tasks: Application.compile_env(:live_admin, :tasks)
}

def start(_type, _args) do
opts = [strategy: :one_for_one, name: LiveAdmin.Supervisor]

Expand All @@ -14,9 +46,32 @@ defmodule LiveAdmin.Application do

NimbleOptions.validate!(Application.get_all_env(:live_admin), global_options_schema)

validate_compile_time_config!()

Supervisor.start_link(children(), opts)
end

@doc false
def validate_compile_time_config! do
@compile_time_app_keys
|> Enum.filter(fn key ->
Map.fetch!(@compile_time_app_config, key) != Application.get_env(:live_admin, key)
end)
|> case do
[] ->
:ok

mismatches ->
raise """
The following :live_admin config keys have been set at runtime, but they must be set at compile time:

#{Enum.map_join(mismatches, "\n", &" * #{inspect(&1)}")}

Move these into config/config.exs (or another compile-time config file).
"""
end
end

defp children do
[
{LiveAdmin.Session.Agent, %{}},
Expand Down
2 changes: 2 additions & 0 deletions lib/live_admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ defmodule LiveAdmin do

Used internally to validate configuration in apps using LiveAdmin.

When set at the application level, every option in this schema *except* `ecto_repo` is read at compile time. Configure them in `config/config.exs` (or another compile-time config file); setting them at runtime will raise on boot.

Supported options:
#{@options_schema |> NimbleOptions.new!() |> NimbleOptions.docs()}
"""
Expand Down
2 changes: 1 addition & 1 deletion lib/live_admin/components/nav.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule LiveAdmin.Components.Nav do
match?(
%{
metadata: %{
phoenix_live_view: {_, _, _, %{extra: %{session: {_, _, [^base_path, _]}}}}
phoenix_live_view: {_, _, _, %{extra: %{session: {_, _, [^base_path, _, _]}}}}
}
},
r
Expand Down
54 changes: 33 additions & 21 deletions lib/live_admin/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,27 @@ defmodule LiveAdmin.Router do

@base_path Path.join(["/", current_path, unquote(path)])
@__live_admin_scope_opts__ unquote(opts)
@__live_admin_app_config__ [
components: Application.compile_env(:live_admin, :components, []),
query_with: Application.compile_env(:live_admin, :query_with),
render_with: Application.compile_env(:live_admin, :render_with),
delete_with: Application.compile_env(:live_admin, :delete_with),
create_with: Application.compile_env(:live_admin, :create_with),
update_with: Application.compile_env(:live_admin, :update_with),
validate_with: Application.compile_env(:live_admin, :validate_with),
label_with: Application.compile_env(:live_admin, :label_with),
title_with: Application.compile_env(:live_admin, :title_with),
hidden_fields: Application.compile_env(:live_admin, :hidden_fields, []),
immutable_fields: Application.compile_env(:live_admin, :immutable_fields, []),
actions: Application.compile_env(:live_admin, :actions, []),
tasks: Application.compile_env(:live_admin, :tasks, [])
]

scope unquote(path), alias: false, as: false do
live_session :"live_admin_#{@base_path}",
session: {unquote(__MODULE__), :build_session, [@base_path, unquote(opts)]},
session:
{unquote(__MODULE__), :build_session,
[@base_path, unquote(opts), @__live_admin_app_config__]},
root_layout: {LiveAdmin.View, :layout},
layout: {LiveAdmin.View, :app},
on_mount: {unquote(__MODULE__), :assign_options} do
Expand Down Expand Up @@ -58,9 +75,7 @@ defmodule LiveAdmin.Router do
LiveAdmin.Router.__validate_config__!(
resource_mod,
@__live_admin_scope_opts__,
create_with: Application.compile_env(:live_admin, :create_with),
update_with: Application.compile_env(:live_admin, :update_with),
components: Application.compile_env(:live_admin, :components, [])
@__live_admin_app_config__
)

full_path = Path.join(@base_path, path)
Expand Down Expand Up @@ -112,7 +127,7 @@ defmodule LiveAdmin.Router do
end)
end

def build_session(conn, base_path, opts) do
def build_session(conn, base_path, opts, app_config) do
opts_schema =
LiveAdmin.base_configs_schema() ++
[title: [type: :string, default: "LiveAdmin"], on_mount: [type: {:tuple, [:atom, :atom]}]]
Expand All @@ -128,7 +143,7 @@ defmodule LiveAdmin.Router do
index: LiveAdmin.Components.Container.Index,
show: LiveAdmin.Components.Container.Show
],
Application.get_env(:live_admin, :components, [])
Keyword.get(app_config, :components, [])
)

opts =
Expand All @@ -139,21 +154,18 @@ defmodule LiveAdmin.Router do
Keyword.merge(default_components, Keyword.get(opts, :components, []))
)
|> Keyword.put_new(:ecto_repo, Application.get_env(:live_admin, :ecto_repo))
|> Keyword.put_new(:render_with, Application.get_env(:live_admin, :render_with))
|> Keyword.put_new(:delete_with, Application.get_env(:live_admin, :delete_with))
|> Keyword.put_new(:create_with, Application.get_env(:live_admin, :create_with))
|> Keyword.put_new(:query_with, Application.get_env(:live_admin, :query_with))
|> Keyword.put_new(:update_with, Application.get_env(:live_admin, :update_with))
|> Keyword.put_new(:label_with, Application.get_env(:live_admin, :label_with))
|> Keyword.put_new(:title_with, Application.get_env(:live_admin, :title_with))
|> Keyword.put_new(:validate_with, Application.get_env(:live_admin, :validate_with))
|> Keyword.put_new(:hidden_fields, Application.get_env(:live_admin, :hidden_fields, []))
|> Keyword.put_new(
:immutable_fields,
Application.get_env(:live_admin, :immutable_fields, [])
)
|> Keyword.put_new(:actions, Application.get_env(:live_admin, :actions, []))
|> Keyword.put_new(:tasks, Application.get_env(:live_admin, :tasks, []))
|> Keyword.put_new(:render_with, Keyword.get(app_config, :render_with))
|> Keyword.put_new(:delete_with, Keyword.get(app_config, :delete_with))
|> Keyword.put_new(:create_with, Keyword.get(app_config, :create_with))
|> Keyword.put_new(:query_with, Keyword.get(app_config, :query_with))
|> Keyword.put_new(:update_with, Keyword.get(app_config, :update_with))
|> Keyword.put_new(:label_with, Keyword.get(app_config, :label_with))
|> Keyword.put_new(:title_with, Keyword.get(app_config, :title_with))
|> Keyword.put_new(:validate_with, Keyword.get(app_config, :validate_with))
|> Keyword.put_new(:hidden_fields, Keyword.get(app_config, :hidden_fields, []))
|> Keyword.put_new(:immutable_fields, Keyword.get(app_config, :immutable_fields, []))
|> Keyword.put_new(:actions, Keyword.get(app_config, :actions, []))
|> Keyword.put_new(:tasks, Keyword.get(app_config, :tasks, []))

%{
"session_id" => LiveAdmin.session_store().init!(conn),
Expand Down
24 changes: 24 additions & 0 deletions test/live_admin/application_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule LiveAdmin.ApplicationTest do
use ExUnit.Case, async: false

describe "validate_compile_time_config! when a resource option is set at runtime" do
setup do
Application.put_env(:live_admin, :create_with, {RuntimeMod, :runtime_fun})
on_exit(fn -> Application.delete_env(:live_admin, :create_with) end)

:ok
end

test "raises pointing the user to compile-time config" do
assert_raise RuntimeError, ~r/create_with/, fn ->
LiveAdmin.Application.validate_compile_time_config!()
end
end
end

describe "validate_compile_time_config! when no resource options diverge between compile and runtime env" do
test "returns :ok" do
assert :ok == LiveAdmin.Application.validate_compile_time_config!()
end
end
end
54 changes: 54 additions & 0 deletions test/live_admin/router_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,62 @@
defmodule LiveAdmin.RouterTest do
use ExUnit.Case, async: true

import Mox

alias LiveAdmin.Router

setup :verify_on_exit!

describe "build_session/4 with a compile-time resource option in app_config" do
setup do
stub_with(LiveAdminTest.MockSession, LiveAdminTest.StubSession)

session =
Router.build_session(
%Plug.Conn{},
"/admin",
[],
create_with: {SomeMod, :some_fun}
)

%{session: session}
end

test "resolves the option from app_config", %{session: session} do
assert session["opts"][:create_with] == {SomeMod, :some_fun}
end
end

describe "build_session/4 when a compile-time resource option is set in runtime Application env" do
setup do
stub_with(LiveAdminTest.MockSession, LiveAdminTest.StubSession)

Application.put_env(:live_admin, :create_with, {RuntimeMod, :runtime_fun})
on_exit(fn -> Application.delete_env(:live_admin, :create_with) end)

session = Router.build_session(%Plug.Conn{}, "/admin", [], [])

%{session: session}
end

test "ignores the runtime value", %{session: session} do
refute session["opts"][:create_with]
end
end

describe "build_session/4 list-typed resource options not provided in app_config" do
setup do
stub_with(LiveAdminTest.MockSession, LiveAdminTest.StubSession)

session = Router.build_session(%Plug.Conn{}, "/admin", [], [])
%{session: session}
end

test "default to an empty list", %{session: session} do
assert session["opts"][:hidden_fields] == []
end
end

describe "create_with: false and custom :create component at the resource level" do
test "raises ArgumentError" do
assert_raise ArgumentError, ~r/create_with: false.*:create component/, fn ->
Expand Down
Loading