Current section

Files

Jump to
ash_admin lib ash_admin resource resource.ex
Raw

lib/ash_admin/resource/resource.ex

defmodule AshAdmin.Resource do
@field %Spark.Dsl.Entity{
describe: "Declare non-default behavior for a specific attribute.",
name: :field,
schema: AshAdmin.Resource.Field.schema(),
target: AshAdmin.Resource.Field,
args: [:name]
}
@form %Spark.Dsl.Section{
describe: "Configure the appearance of fields in admin forms.",
name: :form,
entities: [
@field
]
}
@admin %Spark.Dsl.Section{
describe: "Configure the admin dashboard for a given resource.",
name: :admin,
sections: [
@form
],
schema: [
name: [
type: :string,
doc: "The proper name to use when this resource appears in the admin interface."
],
actor?: [
type: :boolean,
doc: "Whether or not this resource can be used as the actor for requests."
],
show_action: [
type: :atom,
doc:
"The action to use when linking to the resource/viewing a single record. Defaults to the primary read action."
],
read_actions: [
type: {:list, :atom},
doc:
"A list of read actions that can be used to show resource details. By default, all actions are included."
],
generic_actions: [
type: {:list, :atom},
doc:
"A list of generic actions that can be used to show resource details. By default, all actions are included."
],
create_actions: [
type: {:list, :atom},
doc:
"A list of create actions that can create records. By default, all actions are included."
],
update_actions: [
type: {:list, :atom},
doc:
"A list of update actions that can be used to update records. By default, all actions are included."
],
destroy_actions: [
type: {:list, :atom},
doc:
"A list of destroy actions that can be used to destroy records. By default, all actions are included."
],
polymorphic_tables: [
type: {:list, :string},
doc: """
For resources that use ash_postgres' polymorphism capabilities, you can provide a list of tables that should be available to select. These will be added to the list of derivable tables based on scanning all domains and resources provided to ash_admin.
"""
],
polymorphic_actions: [
type: {:list, :atom},
doc: """
For resources that use ash_postgres' polymorphism capabilities, you can provide a list of actions that should require a table to be set. If this is not set, then *all* actions will require tables.
"""
],
table_columns: [
type: {:list, :atom},
doc: "The list of attributes to render on the table view."
],
format_fields: [
type: {:list, :any},
doc: """
The list of fields and their formats represented as a MFA. For example: `updated_at: {Timex, :format!, ["{0D}-{0M}-{YYYY} {h12}:{m} {AM}"]}`. Datatable pages format all given fields. Show and Update pages format given read-only fields of types `Ash.Type.Date`, `Ash.Type.DateTime`, `Ash.Type.Time`, `Ash.Type.NaiveDatetime`, `Ash.Type.UtcDatetime` and `Ash.Type.UtcDatetimeUsec`.
"""
],
relationship_display_fields: [
type: {:list, :atom},
doc:
"The list of attributes to render when this resource is shown as a relationship on another resource's datatable."
],
resource_group: [
type: :atom,
doc: "The group in the top resource dropdown that the resource appears in."
],
show_sensitive_fields: [
type: {:list, :atom},
doc:
"The list of fields that should not be redacted in the admin UI even if they are marked as sensitive."
],
show_calculations: [
type: {:list, :atom},
doc:
"A list of calculation that can be calculate when this resource is shown. By default, all calculations are included."
],
label_field: [
type: :atom,
doc:
"The field to use as the label when the resource appears in a relationship select or typeahead field on another resource's form."
],
relationship_select_max_items: [
type: :integer,
default: 50,
doc:
"The maximum number of items to show in a select field when this resource is shown as a relationship on another resource's form. If the number of related resources is higher, a typeahead selector will be used."
]
]
}
use Spark.Dsl.Extension,
sections: [@admin],
transformers: [
AshAdmin.Resource.Transformers.ValidateTableColumns,
AshAdmin.Resource.Transformers.AddPositionSortCalculation
],
verifiers: [
AshAdmin.Resource.Verifiers.VerifyFileArgumentsExist
]
@moduledoc """
A resource extension to alter the behaviour of a resource in the admin UI.
"""
def polymorphic?(resource, domains) do
polymorphic_tables(resource, domains) not in [nil, []]
end
def polymorphic_tables(resource, domains) do
resource
|> Spark.Dsl.Extension.get_opt([:admin], :polymorphic_tables, [], true)
|> Enum.concat(find_polymorphic_tables(resource, domains))
|> Enum.uniq()
end
def polymorphic_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :polymorphic_actions, nil, true)
end
def relationship_display_fields(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :relationship_display_fields, nil, true)
end
def table_columns(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :table_columns, nil, true) ||
Enum.map(Ash.Resource.Info.attributes(resource), & &1.name)
end
def format_fields(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :format_fields, nil, true)
end
def name(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :name, nil, true) ||
resource
|> Module.split()
|> List.last()
end
def resource_group(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :resource_group, nil, true)
end
def show_sensitive_fields(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :show_sensitive_fields, [], true)
end
def label_field(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :label_field, nil, true)
end
def relationship_select_max_items(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :relationship_select_max_items, 50, true)
end
def actor?(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :actor?, false, true)
end
def read_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :read_actions, nil, true) ||
actions_with_primary_first(resource, :read)
end
def generic_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :generic_actions, nil, true) ||
actions_with_primary_first(resource, :action)
end
def create_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :create_actions, nil, true) ||
actions_with_primary_first(resource, :create)
end
def update_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :update_actions, nil, true) ||
actions_with_primary_first(resource, :update)
end
def destroy_actions(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :destroy_actions, nil, true) ||
actions_with_primary_first(resource, :destroy)
end
def show_action(resource) do
action = Spark.Dsl.Extension.get_opt(resource, [:admin], :show_action, false, [])
if action do
action
else
action = AshAdmin.Helpers.primary_action(resource, :read)
action && action.name
end
end
def show_calculations(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :show_calculations, [], true)
end
def fields(resource) do
Spark.Dsl.Extension.get_entities(resource, [:admin, :form])
end
def field(resource, name) do
resource
|> fields()
|> Enum.find(fn field ->
field.name == name
end)
end
defp find_polymorphic_tables(resource, domains) do
domains
|> 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])
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end
defp actions_with_primary_first(resource, type) do
resource
|> Ash.Resource.Info.actions()
|> Enum.filter(&(&1.type == type))
|> Enum.sort_by(&(!Map.get(&1, :primary?)))
|> Enum.map(& &1.name)
end
end